mirror of
https://github.com/mii443/wasmer.git
synced 2025-09-04 08:29:16 +00:00
Merge branch 'wasix-core-changes' into wasix
This commit is contained in:
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@ -213,9 +213,6 @@ jobs:
|
|||||||
# shell: bash
|
# shell: bash
|
||||||
# run: |
|
# run: |
|
||||||
# make build-wasmer-wasm
|
# make build-wasmer-wasm
|
||||||
- name: Build Wapm binary
|
|
||||||
run: |
|
|
||||||
make build-wapm
|
|
||||||
- name: Install Nightly Rust for Headless
|
- name: Install Nightly Rust for Headless
|
||||||
uses: dtolnay/rust-toolchain@nightly
|
uses: dtolnay/rust-toolchain@nightly
|
||||||
with:
|
with:
|
||||||
|
8
.github/workflows/cloudcompiler.yaml
vendored
8
.github/workflows/cloudcompiler.yaml
vendored
@ -35,7 +35,7 @@ jobs:
|
|||||||
make build-wasmer-wasm &&
|
make build-wasmer-wasm &&
|
||||||
mkdir target/wasm32-wasi/release/cloudcompiler
|
mkdir target/wasm32-wasi/release/cloudcompiler
|
||||||
cp target/wasm32-wasi/release/wasmer-compiler.wasm target/wasm32-wasi/release/cloudcompiler/cloudcompiler.wasm &&
|
cp target/wasm32-wasi/release/wasmer-compiler.wasm target/wasm32-wasi/release/cloudcompiler/cloudcompiler.wasm &&
|
||||||
cat << EOF > target/wasm32-wasi/release/cloudcompiler/wapm.toml
|
cat << EOF > target/wasm32-wasi/release/cloudcompiler/wasmer.toml
|
||||||
[package]
|
[package]
|
||||||
name = "${{ secrets.WAPM_DEV_USERNAME }}/cloudcompiler"
|
name = "${{ secrets.WAPM_DEV_USERNAME }}/cloudcompiler"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -55,17 +55,17 @@ jobs:
|
|||||||
name = "cloudcompiler"
|
name = "cloudcompiler"
|
||||||
module = "cloudcompiler"
|
module = "cloudcompiler"
|
||||||
EOF
|
EOF
|
||||||
- name: Fix wapm.toml version
|
- name: Fix wasmer.toml version
|
||||||
run: |
|
run: |
|
||||||
echo $(git tag | tail -n1) > ./version.txt
|
echo $(git tag | tail -n1) > ./version.txt
|
||||||
v=$(git describe --tags --abbrev=0) && \
|
v=$(git describe --tags --abbrev=0) && \
|
||||||
echo "version = ${v}" &&
|
echo "version = ${v}" &&
|
||||||
sed -i "s/version = \".*\"/version = \"${v}\"/g" target/wasm32-wasi/release/cloudcompiler/wapm.toml \
|
sed -i "s/version = \".*\"/version = \"${v}\"/g" target/wasm32-wasi/release/cloudcompiler/wasmer.toml \
|
||||||
- name: Build cloudcompiler.wasm
|
- name: Build cloudcompiler.wasm
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
git tag &&
|
git tag &&
|
||||||
cat target/wasm32-wasi/release/cloudcompiler/wapm.toml &&
|
cat target/wasm32-wasi/release/cloudcompiler/wasmer.toml &&
|
||||||
echo "ls" &&
|
echo "ls" &&
|
||||||
ls target/wasm32-wasi/release/cloudcompiler
|
ls target/wasm32-wasi/release/cloudcompiler
|
||||||
- name: Publish to WAPM
|
- name: Publish to WAPM
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -13,6 +13,8 @@ api-docs-repo/
|
|||||||
/src/windows-installer/WasmerInstaller.exe
|
/src/windows-installer/WasmerInstaller.exe
|
||||||
/lib/c-api/wasmer.h
|
/lib/c-api/wasmer.h
|
||||||
.xwin-cache
|
.xwin-cache
|
||||||
|
wapm.toml
|
||||||
|
wasmer.toml
|
||||||
# Generated by tests on Android
|
# Generated by tests on Android
|
||||||
/avd
|
/avd
|
||||||
/core
|
/core
|
||||||
|
@ -9,6 +9,10 @@ Looking for changes that affect our C API? See the [C API Changelog](lib/c-api/C
|
|||||||
|
|
||||||
## **Unreleased**
|
## **Unreleased**
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- [#3439](https://github.com/wasmerio/wasmer/pull/3439) Use GNU/Linux frame registration code for FreeBSD too
|
||||||
|
|
||||||
## 3.1.0 - 12/12/2022
|
## 3.1.0 - 12/12/2022
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
583
Cargo.lock
generated
583
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -49,6 +49,7 @@ members = [
|
|||||||
"lib/wasi-experimental-io-devices",
|
"lib/wasi-experimental-io-devices",
|
||||||
"lib/wasi-local-networking",
|
"lib/wasi-local-networking",
|
||||||
"lib/wasix/wasix-http-client",
|
"lib/wasix/wasix-http-client",
|
||||||
|
"lib/wasm-interface",
|
||||||
"lib/c-api/tests/wasmer-c-api-test-runner",
|
"lib/c-api/tests/wasmer-c-api-test-runner",
|
||||||
"lib/c-api/examples/wasmer-capi-examples-runner",
|
"lib/c-api/examples/wasmer-capi-examples-runner",
|
||||||
"lib/types",
|
"lib/types",
|
||||||
|
30
Makefile
30
Makefile
@ -401,18 +401,6 @@ else
|
|||||||
strip --strip-unneeded target/$(HOST_TARGET)/release/wasmer-headless
|
strip --strip-unneeded target/$(HOST_TARGET)/release/wasmer-headless
|
||||||
endif
|
endif
|
||||||
|
|
||||||
WAPM_VERSION = v0.5.3
|
|
||||||
get-wapm:
|
|
||||||
[ -d "wapm-cli" ] || git clone --branch $(WAPM_VERSION) https://github.com/wasmerio/wapm-cli.git
|
|
||||||
|
|
||||||
build-wapm: get-wapm
|
|
||||||
ifeq ($(IS_DARWIN), 1)
|
|
||||||
# We build it without bundling sqlite, as is included by default in macos
|
|
||||||
$(CARGO_BINARY) build $(CARGO_TARGET) --release --manifest-path wapm-cli/Cargo.toml --no-default-features --features "full packagesigning telemetry update-notifications"
|
|
||||||
else
|
|
||||||
$(CARGO_BINARY) build $(CARGO_TARGET) --release --manifest-path wapm-cli/Cargo.toml --features "telemetry update-notifications"
|
|
||||||
endif
|
|
||||||
|
|
||||||
build-docs:
|
build-docs:
|
||||||
$(CARGO_BINARY) doc $(CARGO_TARGET) --release $(compiler_features) --document-private-items --no-deps --workspace --exclude wasmer-c-api
|
$(CARGO_BINARY) doc $(CARGO_TARGET) --release $(compiler_features) --document-private-items --no-deps --workspace --exclude wasmer-c-api
|
||||||
|
|
||||||
@ -552,6 +540,7 @@ test-examples:
|
|||||||
$(CARGO_BINARY) test $(CARGO_TARGET) --release $(compiler_features) --features wasi --examples
|
$(CARGO_BINARY) test $(CARGO_TARGET) --release $(compiler_features) --features wasi --examples
|
||||||
|
|
||||||
test-integration-cli:
|
test-integration-cli:
|
||||||
|
rustup target add wasm32-wasi
|
||||||
$(CARGO_BINARY) test $(CARGO_TARGET) --features webc_runner --no-fail-fast -p wasmer-integration-tests-cli -- --nocapture --test-threads=1
|
$(CARGO_BINARY) test $(CARGO_TARGET) --features webc_runner --no-fail-fast -p wasmer-integration-tests-cli -- --nocapture --test-threads=1
|
||||||
|
|
||||||
test-integration-ios:
|
test-integration-ios:
|
||||||
@ -567,23 +556,6 @@ generate-wasi-tests:
|
|||||||
#
|
#
|
||||||
#####
|
#####
|
||||||
|
|
||||||
package-wapm:
|
|
||||||
mkdir -p "package/bin"
|
|
||||||
ifneq (, $(filter 1, $(IS_DARWIN) $(IS_LINUX) $(IS_FREEBSD)))
|
|
||||||
if [ -d "wapm-cli" ]; then \
|
|
||||||
cp wapm-cli/$(TARGET_DIR)/wapm package/bin/ ;\
|
|
||||||
echo -e "#!/bin/bash\nwapm execute \"\$$@\"" > package/bin/wax ;\
|
|
||||||
chmod +x package/bin/wax ;\
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [ -d "wapm-cli" ]; then \
|
|
||||||
cp wapm-cli/$(TARGET_DIR)/wapm package/bin/ ;\
|
|
||||||
fi
|
|
||||||
ifeq ($(IS_DARWIN), 1)
|
|
||||||
codesign -s - package/bin/wapm || true
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
package-minimal-headless-wasmer:
|
package-minimal-headless-wasmer:
|
||||||
ifeq ($(IS_WINDOWS), 1)
|
ifeq ($(IS_WINDOWS), 1)
|
||||||
if [ -f "target/$(HOST_TARGET)/release/wasmer-headless.exe" ]; then \
|
if [ -f "target/$(HOST_TARGET)/release/wasmer-headless.exe" ]; then \
|
||||||
|
18
deny.toml
18
deny.toml
@ -49,7 +49,13 @@ notice = "warn"
|
|||||||
# output a note when they are encountered.
|
# output a note when they are encountered.
|
||||||
ignore = [
|
ignore = [
|
||||||
#"RUSTSEC-0000-0000",
|
#"RUSTSEC-0000-0000",
|
||||||
|
|
||||||
]
|
]
|
||||||
|
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||||
|
# If this is false, then it uses a built-in git library.
|
||||||
|
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
|
||||||
|
# See Git Authentication for more information about setting up git authentication.
|
||||||
|
git-fetch-with-cli = true
|
||||||
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
|
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
|
||||||
# lower than the range specified will be ignored. Note that ignored advisories
|
# lower than the range specified will be ignored. Note that ignored advisories
|
||||||
# will still output a note when they are encountered.
|
# will still output a note when they are encountered.
|
||||||
@ -105,13 +111,21 @@ confidence-threshold = 0.8
|
|||||||
exceptions = [
|
exceptions = [
|
||||||
# Each entry is the crate and version constraint, and its specific allow
|
# Each entry is the crate and version constraint, and its specific allow
|
||||||
# list
|
# list
|
||||||
{ allow = ["LicenseRef-LICENSE.txt"], name = "webc", version = "*" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Some crates don't have (easily) machine readable licensing information,
|
# Some crates don't have (easily) machine readable licensing information,
|
||||||
# adding a clarification entry for it allows you to manually specify the
|
# adding a clarification entry for it allows you to manually specify the
|
||||||
# licensing information
|
# licensing information
|
||||||
#[[licenses.clarify]]
|
[[licenses.clarify]]
|
||||||
|
name = "webc"
|
||||||
|
version = "*"
|
||||||
|
expression = "BSL-1.0"
|
||||||
|
license-files = [
|
||||||
|
{ path = "LICENSE.txt", hash = 0xa2180a97 }
|
||||||
|
]
|
||||||
|
|
||||||
# The name of the crate the clarification applies to
|
# The name of the crate the clarification applies to
|
||||||
#name = "ring"
|
#name = "ring"
|
||||||
# The optional version constraint for the crate
|
# The optional version constraint for the crate
|
||||||
|
1
lib/api/src/js/externals/memory.rs
vendored
1
lib/api/src/js/externals/memory.rs
vendored
@ -93,7 +93,6 @@ impl Memory {
|
|||||||
pub(crate) fn new_internal(ty: MemoryType) -> Result<js_sys::WebAssembly::Memory, MemoryError> {
|
pub(crate) fn new_internal(ty: MemoryType) -> Result<js_sys::WebAssembly::Memory, MemoryError> {
|
||||||
let descriptor = js_sys::Object::new();
|
let descriptor = js_sys::Object::new();
|
||||||
#[allow(unused_unsafe)]
|
#[allow(unused_unsafe)]
|
||||||
#[allow(unused_unsafe)]
|
|
||||||
unsafe {
|
unsafe {
|
||||||
js_sys::Reflect::set(&descriptor, &"initial".into(), &ty.minimum.0.into()).unwrap();
|
js_sys::Reflect::set(&descriptor, &"initial".into(), &ty.minimum.0.into()).unwrap();
|
||||||
if let Some(max) = ty.maximum {
|
if let Some(max) = ty.maximum {
|
||||||
|
@ -629,15 +629,6 @@ impl Module {
|
|||||||
ExportsIterator::new(iter, exports.length() as usize)
|
ExportsIterator::new(iter, exports.length() as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the module is still ok - this will be
|
|
||||||
/// false if the module was passed between threads in a
|
|
||||||
/// way that it became undefined (JS does not share objects
|
|
||||||
/// between threads except via a post_message())
|
|
||||||
pub fn is_ok(&self) -> bool {
|
|
||||||
let val = JsValue::from(&self.module);
|
|
||||||
!val.is_undefined() && !val.is_null()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the custom sections of the module given a `name`.
|
/// Get the custom sections of the module given a `name`.
|
||||||
///
|
///
|
||||||
/// # Important
|
/// # Important
|
||||||
|
31
lib/api/src/sys/externals/function.rs
vendored
31
lib/api/src/sys/externals/function.rs
vendored
@ -1,34 +1,31 @@
|
|||||||
|
use wasmer_types::RawValue;
|
||||||
|
use wasmer_vm::{
|
||||||
|
on_host_stack, raise_user_trap, resume_panic, InternalStoreHandle, StoreHandle, VMContext,
|
||||||
|
VMDynamicFunctionContext, VMExtern, VMFuncRef, VMFunction, VMFunctionBody, VMFunctionKind,
|
||||||
|
VMTrampoline,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::sys::exports::{ExportError, Exportable};
|
use crate::sys::exports::{ExportError, Exportable};
|
||||||
use crate::sys::externals::Extern;
|
use crate::sys::externals::Extern;
|
||||||
use crate::sys::store::{AsStoreMut, AsStoreRef};
|
use crate::sys::store::{AsStoreMut, AsStoreRef};
|
||||||
use crate::sys::FunctionType;
|
use crate::sys::{FunctionType, RuntimeError, TypedFunction};
|
||||||
use crate::sys::TypedFunction;
|
|
||||||
use crate::FunctionEnv;
|
use crate::FunctionEnv;
|
||||||
|
|
||||||
pub use inner::{FromToNativeWasmType, HostFunction, WasmTypeList, WithEnv, WithoutEnv};
|
pub use inner::{FromToNativeWasmType, HostFunction, WasmTypeList, WithEnv, WithoutEnv};
|
||||||
|
|
||||||
#[cfg(feature = "compiler")]
|
#[cfg(feature = "compiler")]
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
sys::{
|
sys::store::{StoreInner, StoreMut},
|
||||||
store::{StoreInner, StoreMut},
|
|
||||||
RuntimeError,
|
|
||||||
},
|
|
||||||
FunctionEnvMut, Value,
|
FunctionEnvMut, Value,
|
||||||
},
|
},
|
||||||
inner::StaticFunction,
|
inner::StaticFunction,
|
||||||
std::{cell::UnsafeCell, cmp::max, ffi::c_void},
|
std::{cell::UnsafeCell, cmp::max, ffi::c_void},
|
||||||
wasmer_types::RawValue,
|
|
||||||
wasmer_vm::{
|
wasmer_vm::{
|
||||||
on_host_stack, raise_user_trap, resume_panic, wasmer_call_trampoline, MaybeInstanceOwned,
|
wasmer_call_trampoline, MaybeInstanceOwned, VMCallerCheckedAnyfunc, VMFunctionContext,
|
||||||
VMCallerCheckedAnyfunc, VMContext, VMDynamicFunctionContext, VMFunctionBody,
|
|
||||||
VMFunctionContext, VMTrampoline,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use wasmer_vm::{
|
|
||||||
InternalStoreHandle, StoreHandle, VMExtern, VMFuncRef, VMFunction, VMFunctionKind,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A WebAssembly `function` instance.
|
/// A WebAssembly `function` instance.
|
||||||
///
|
///
|
||||||
/// A function instance is the runtime representation of a function.
|
/// A function instance is the runtime representation of a function.
|
||||||
@ -803,19 +800,16 @@ impl<'a> Exportable<'a> for Function {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Host state for a dynamic function.
|
/// Host state for a dynamic function.
|
||||||
#[cfg(feature = "compiler")]
|
|
||||||
pub(crate) struct DynamicFunction<F> {
|
pub(crate) struct DynamicFunction<F> {
|
||||||
func: F,
|
func: F,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "compiler")]
|
|
||||||
impl<F> DynamicFunction<F>
|
impl<F> DynamicFunction<F>
|
||||||
where
|
where
|
||||||
F: Fn(*mut RawValue) -> Result<(), RuntimeError> + 'static,
|
F: Fn(*mut RawValue) -> Result<(), RuntimeError> + 'static,
|
||||||
{
|
{
|
||||||
// This function wraps our func, to make it compatible with the
|
// This function wraps our func, to make it compatible with the
|
||||||
// reverse trampoline signature
|
// reverse trampoline signature
|
||||||
#[cfg(feature = "compiler")]
|
|
||||||
unsafe extern "C" fn func_wrapper(
|
unsafe extern "C" fn func_wrapper(
|
||||||
this: &mut VMDynamicFunctionContext<Self>,
|
this: &mut VMDynamicFunctionContext<Self>,
|
||||||
values_vec: *mut RawValue,
|
values_vec: *mut RawValue,
|
||||||
@ -832,12 +826,10 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "compiler")]
|
|
||||||
fn func_body_ptr(&self) -> *const VMFunctionBody {
|
fn func_body_ptr(&self) -> *const VMFunctionBody {
|
||||||
Self::func_wrapper as *const VMFunctionBody
|
Self::func_wrapper as *const VMFunctionBody
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "compiler")]
|
|
||||||
fn call_trampoline_address(&self) -> VMTrampoline {
|
fn call_trampoline_address(&self) -> VMTrampoline {
|
||||||
Self::call_trampoline
|
Self::call_trampoline
|
||||||
}
|
}
|
||||||
@ -870,7 +862,6 @@ mod inner {
|
|||||||
use crate::sys::NativeWasmTypeInto;
|
use crate::sys::NativeWasmTypeInto;
|
||||||
use crate::{AsStoreMut, AsStoreRef, ExternRef, FunctionEnv, StoreMut};
|
use crate::{AsStoreMut, AsStoreRef, ExternRef, FunctionEnv, StoreMut};
|
||||||
|
|
||||||
#[cfg(feature = "compiler")]
|
|
||||||
use crate::Function;
|
use crate::Function;
|
||||||
|
|
||||||
/// A trait to convert a Rust value to a `WasmNativeType` value,
|
/// A trait to convert a Rust value to a `WasmNativeType` value,
|
||||||
|
@ -267,11 +267,11 @@ impl Module {
|
|||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub unsafe fn deserialize(
|
pub unsafe fn deserialize(
|
||||||
store: &impl AsStoreRef,
|
engine: &impl AsEngineRef,
|
||||||
bytes: impl IntoBytes,
|
bytes: impl IntoBytes,
|
||||||
) -> Result<Self, DeserializeError> {
|
) -> Result<Self, DeserializeError> {
|
||||||
let bytes = bytes.into_bytes();
|
let bytes = bytes.into_bytes();
|
||||||
let artifact = store.as_store_ref().engine().deserialize(&bytes)?;
|
let artifact = engine.as_engine_ref().engine().deserialize(&bytes)?;
|
||||||
Ok(Self::from_artifact(artifact))
|
Ok(Self::from_artifact(artifact))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,11 +294,11 @@ impl Module {
|
|||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub unsafe fn deserialize_from_file(
|
pub unsafe fn deserialize_from_file(
|
||||||
store: &impl AsStoreRef,
|
engine: &impl AsEngineRef,
|
||||||
path: impl AsRef<Path>,
|
path: impl AsRef<Path>,
|
||||||
) -> Result<Self, DeserializeError> {
|
) -> Result<Self, DeserializeError> {
|
||||||
let artifact = store
|
let artifact = engine
|
||||||
.as_store_ref()
|
.as_engine_ref()
|
||||||
.engine()
|
.engine()
|
||||||
.deserialize_from_file(path.as_ref())?;
|
.deserialize_from_file(path.as_ref())?;
|
||||||
Ok(Self::from_artifact(artifact))
|
Ok(Self::from_artifact(artifact))
|
||||||
@ -453,15 +453,6 @@ impl Module {
|
|||||||
self.module_info.exports()
|
self.module_info.exports()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the module is still ok - this will be
|
|
||||||
/// false if the module was passed between threads in a
|
|
||||||
/// way that it became undefined (JS does not share objects
|
|
||||||
/// between threads except via a post_message())
|
|
||||||
pub fn is_ok(&self) -> bool {
|
|
||||||
// As RUST is a type safe language modules in SYS are always ok
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the custom sections of the module given a `name`.
|
/// Get the custom sections of the module given a `name`.
|
||||||
///
|
///
|
||||||
/// # Important
|
/// # Important
|
||||||
|
@ -210,28 +210,25 @@ fn memory_grow() -> Result<(), String> {
|
|||||||
fn function_new() -> Result<(), String> {
|
fn function_new() -> Result<(), String> {
|
||||||
let mut store = Store::default();
|
let mut store = Store::default();
|
||||||
let function = Function::new_typed(&mut store, || {});
|
let function = Function::new_typed(&mut store, || {});
|
||||||
assert_eq!(
|
assert_eq!(function.ty(&mut store), FunctionType::new(vec![], vec![]));
|
||||||
function.ty(&mut store).clone(),
|
|
||||||
FunctionType::new(vec![], vec![])
|
|
||||||
);
|
|
||||||
let function = Function::new_typed(&mut store, |_a: i32| {});
|
let function = Function::new_typed(&mut store, |_a: i32| {});
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
function.ty(&mut store).clone(),
|
function.ty(&mut store),
|
||||||
FunctionType::new(vec![Type::I32], vec![])
|
FunctionType::new(vec![Type::I32], vec![])
|
||||||
);
|
);
|
||||||
let function = Function::new_typed(&mut store, |_a: i32, _b: i64, _c: f32, _d: f64| {});
|
let function = Function::new_typed(&mut store, |_a: i32, _b: i64, _c: f32, _d: f64| {});
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
function.ty(&mut store).clone(),
|
function.ty(&mut store),
|
||||||
FunctionType::new(vec![Type::I32, Type::I64, Type::F32, Type::F64], vec![])
|
FunctionType::new(vec![Type::I32, Type::I64, Type::F32, Type::F64], vec![])
|
||||||
);
|
);
|
||||||
let function = Function::new_typed(&mut store, || -> i32 { 1 });
|
let function = Function::new_typed(&mut store, || -> i32 { 1 });
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
function.ty(&mut store).clone(),
|
function.ty(&mut store),
|
||||||
FunctionType::new(vec![], vec![Type::I32])
|
FunctionType::new(vec![], vec![Type::I32])
|
||||||
);
|
);
|
||||||
let function = Function::new_typed(&mut store, || -> (i32, i64, f32, f64) { (1, 2, 3.0, 4.0) });
|
let function = Function::new_typed(&mut store, || -> (i32, i64, f32, f64) { (1, 2, 3.0, 4.0) });
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
function.ty(&mut store).clone(),
|
function.ty(&mut store),
|
||||||
FunctionType::new(vec![], vec![Type::I32, Type::I64, Type::F32, Type::F64])
|
FunctionType::new(vec![], vec![Type::I32, Type::I64, Type::F32, Type::F64])
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -246,14 +243,11 @@ fn function_new_env() -> Result<(), String> {
|
|||||||
let my_env = MyEnv {};
|
let my_env = MyEnv {};
|
||||||
let env = FunctionEnv::new(&mut store, my_env);
|
let env = FunctionEnv::new(&mut store, my_env);
|
||||||
let function = Function::new_typed_with_env(&mut store, &env, |_env: FunctionEnvMut<MyEnv>| {});
|
let function = Function::new_typed_with_env(&mut store, &env, |_env: FunctionEnvMut<MyEnv>| {});
|
||||||
assert_eq!(
|
assert_eq!(function.ty(&mut store), FunctionType::new(vec![], vec![]));
|
||||||
function.ty(&mut store).clone(),
|
|
||||||
FunctionType::new(vec![], vec![])
|
|
||||||
);
|
|
||||||
let function =
|
let function =
|
||||||
Function::new_typed_with_env(&mut store, &env, |_env: FunctionEnvMut<MyEnv>, _a: i32| {});
|
Function::new_typed_with_env(&mut store, &env, |_env: FunctionEnvMut<MyEnv>, _a: i32| {});
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
function.ty(&mut store).clone(),
|
function.ty(&mut store),
|
||||||
FunctionType::new(vec![Type::I32], vec![])
|
FunctionType::new(vec![Type::I32], vec![])
|
||||||
);
|
);
|
||||||
let function = Function::new_typed_with_env(
|
let function = Function::new_typed_with_env(
|
||||||
@ -262,13 +256,13 @@ fn function_new_env() -> Result<(), String> {
|
|||||||
|_env: FunctionEnvMut<MyEnv>, _a: i32, _b: i64, _c: f32, _d: f64| {},
|
|_env: FunctionEnvMut<MyEnv>, _a: i32, _b: i64, _c: f32, _d: f64| {},
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
function.ty(&mut store).clone(),
|
function.ty(&mut store),
|
||||||
FunctionType::new(vec![Type::I32, Type::I64, Type::F32, Type::F64], vec![])
|
FunctionType::new(vec![Type::I32, Type::I64, Type::F32, Type::F64], vec![])
|
||||||
);
|
);
|
||||||
let function =
|
let function =
|
||||||
Function::new_typed_with_env(&mut store, &env, |_env: FunctionEnvMut<MyEnv>| -> i32 { 1 });
|
Function::new_typed_with_env(&mut store, &env, |_env: FunctionEnvMut<MyEnv>| -> i32 { 1 });
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
function.ty(&mut store).clone(),
|
function.ty(&mut store),
|
||||||
FunctionType::new(vec![], vec![Type::I32])
|
FunctionType::new(vec![], vec![Type::I32])
|
||||||
);
|
);
|
||||||
let function = Function::new_typed_with_env(
|
let function = Function::new_typed_with_env(
|
||||||
@ -277,7 +271,7 @@ fn function_new_env() -> Result<(), String> {
|
|||||||
|_env: FunctionEnvMut<MyEnv>| -> (i32, i64, f32, f64) { (1, 2, 3.0, 4.0) },
|
|_env: FunctionEnvMut<MyEnv>| -> (i32, i64, f32, f64) { (1, 2, 3.0, 4.0) },
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
function.ty(&mut store).clone(),
|
function.ty(&mut store),
|
||||||
FunctionType::new(vec![], vec![Type::I32, Type::I64, Type::F32, Type::F64])
|
FunctionType::new(vec![], vec![Type::I32, Type::I64, Type::F32, Type::F64])
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -294,35 +288,35 @@ fn function_new_dynamic() -> Result<(), String> {
|
|||||||
&function_type,
|
&function_type,
|
||||||
|_values: &[Value]| unimplemented!(),
|
|_values: &[Value]| unimplemented!(),
|
||||||
);
|
);
|
||||||
assert_eq!(function.ty(&mut store).clone(), function_type);
|
assert_eq!(function.ty(&mut store), function_type);
|
||||||
let function_type = FunctionType::new(vec![Type::I32], vec![]);
|
let function_type = FunctionType::new(vec![Type::I32], vec![]);
|
||||||
let function = Function::new(
|
let function = Function::new(
|
||||||
&mut store,
|
&mut store,
|
||||||
&function_type,
|
&function_type,
|
||||||
|_values: &[Value]| unimplemented!(),
|
|_values: &[Value]| unimplemented!(),
|
||||||
);
|
);
|
||||||
assert_eq!(function.ty(&mut store).clone(), function_type);
|
assert_eq!(function.ty(&mut store), function_type);
|
||||||
let function_type = FunctionType::new(vec![Type::I32, Type::I64, Type::F32, Type::F64], vec![]);
|
let function_type = FunctionType::new(vec![Type::I32, Type::I64, Type::F32, Type::F64], vec![]);
|
||||||
let function = Function::new(
|
let function = Function::new(
|
||||||
&mut store,
|
&mut store,
|
||||||
&function_type,
|
&function_type,
|
||||||
|_values: &[Value]| unimplemented!(),
|
|_values: &[Value]| unimplemented!(),
|
||||||
);
|
);
|
||||||
assert_eq!(function.ty(&mut store).clone(), function_type);
|
assert_eq!(function.ty(&mut store), function_type);
|
||||||
let function_type = FunctionType::new(vec![], vec![Type::I32]);
|
let function_type = FunctionType::new(vec![], vec![Type::I32]);
|
||||||
let function = Function::new(
|
let function = Function::new(
|
||||||
&mut store,
|
&mut store,
|
||||||
&function_type,
|
&function_type,
|
||||||
|_values: &[Value]| unimplemented!(),
|
|_values: &[Value]| unimplemented!(),
|
||||||
);
|
);
|
||||||
assert_eq!(function.ty(&mut store).clone(), function_type);
|
assert_eq!(function.ty(&mut store), function_type);
|
||||||
let function_type = FunctionType::new(vec![], vec![Type::I32, Type::I64, Type::F32, Type::F64]);
|
let function_type = FunctionType::new(vec![], vec![Type::I32, Type::I64, Type::F32, Type::F64]);
|
||||||
let function = Function::new(
|
let function = Function::new(
|
||||||
&mut store,
|
&mut store,
|
||||||
&function_type,
|
&function_type,
|
||||||
|_values: &[Value]| unimplemented!(),
|
|_values: &[Value]| unimplemented!(),
|
||||||
);
|
);
|
||||||
assert_eq!(function.ty(&mut store).clone(), function_type);
|
assert_eq!(function.ty(&mut store), function_type);
|
||||||
|
|
||||||
// Using array signature
|
// Using array signature
|
||||||
let function_type = ([Type::V128], [Type::I32, Type::F32, Type::F64]);
|
let function_type = ([Type::V128], [Type::I32, Type::F32, Type::F64]);
|
||||||
@ -356,7 +350,7 @@ fn function_new_dynamic_env() -> Result<(), String> {
|
|||||||
&function_type,
|
&function_type,
|
||||||
|_env: FunctionEnvMut<MyEnv>, _values: &[Value]| unimplemented!(),
|
|_env: FunctionEnvMut<MyEnv>, _values: &[Value]| unimplemented!(),
|
||||||
);
|
);
|
||||||
assert_eq!(function.ty(&mut store).clone(), function_type);
|
assert_eq!(function.ty(&mut store), function_type);
|
||||||
let function_type = FunctionType::new(vec![Type::I32], vec![]);
|
let function_type = FunctionType::new(vec![Type::I32], vec![]);
|
||||||
let function = Function::new_with_env(
|
let function = Function::new_with_env(
|
||||||
&mut store,
|
&mut store,
|
||||||
@ -364,7 +358,7 @@ fn function_new_dynamic_env() -> Result<(), String> {
|
|||||||
&function_type,
|
&function_type,
|
||||||
|_env: FunctionEnvMut<MyEnv>, _values: &[Value]| unimplemented!(),
|
|_env: FunctionEnvMut<MyEnv>, _values: &[Value]| unimplemented!(),
|
||||||
);
|
);
|
||||||
assert_eq!(function.ty(&mut store).clone(), function_type);
|
assert_eq!(function.ty(&mut store), function_type);
|
||||||
let function_type = FunctionType::new(vec![Type::I32, Type::I64, Type::F32, Type::F64], vec![]);
|
let function_type = FunctionType::new(vec![Type::I32, Type::I64, Type::F32, Type::F64], vec![]);
|
||||||
let function = Function::new_with_env(
|
let function = Function::new_with_env(
|
||||||
&mut store,
|
&mut store,
|
||||||
@ -372,7 +366,7 @@ fn function_new_dynamic_env() -> Result<(), String> {
|
|||||||
&function_type,
|
&function_type,
|
||||||
|_env: FunctionEnvMut<MyEnv>, _values: &[Value]| unimplemented!(),
|
|_env: FunctionEnvMut<MyEnv>, _values: &[Value]| unimplemented!(),
|
||||||
);
|
);
|
||||||
assert_eq!(function.ty(&mut store).clone(), function_type);
|
assert_eq!(function.ty(&mut store), function_type);
|
||||||
let function_type = FunctionType::new(vec![], vec![Type::I32]);
|
let function_type = FunctionType::new(vec![], vec![Type::I32]);
|
||||||
let function = Function::new_with_env(
|
let function = Function::new_with_env(
|
||||||
&mut store,
|
&mut store,
|
||||||
@ -380,7 +374,7 @@ fn function_new_dynamic_env() -> Result<(), String> {
|
|||||||
&function_type,
|
&function_type,
|
||||||
|_env: FunctionEnvMut<MyEnv>, _values: &[Value]| unimplemented!(),
|
|_env: FunctionEnvMut<MyEnv>, _values: &[Value]| unimplemented!(),
|
||||||
);
|
);
|
||||||
assert_eq!(function.ty(&mut store).clone(), function_type);
|
assert_eq!(function.ty(&mut store), function_type);
|
||||||
let function_type = FunctionType::new(vec![], vec![Type::I32, Type::I64, Type::F32, Type::F64]);
|
let function_type = FunctionType::new(vec![], vec![Type::I32, Type::I64, Type::F32, Type::F64]);
|
||||||
let function = Function::new_with_env(
|
let function = Function::new_with_env(
|
||||||
&mut store,
|
&mut store,
|
||||||
@ -388,7 +382,7 @@ fn function_new_dynamic_env() -> Result<(), String> {
|
|||||||
&function_type,
|
&function_type,
|
||||||
|_env: FunctionEnvMut<MyEnv>, _values: &[Value]| unimplemented!(),
|
|_env: FunctionEnvMut<MyEnv>, _values: &[Value]| unimplemented!(),
|
||||||
);
|
);
|
||||||
assert_eq!(function.ty(&mut store).clone(), function_type);
|
assert_eq!(function.ty(&mut store), function_type);
|
||||||
|
|
||||||
// Using array signature
|
// Using array signature
|
||||||
let function_type = ([Type::V128], [Type::I32, Type::F32, Type::F64]);
|
let function_type = ([Type::V128], [Type::I32, Type::F32, Type::F64]);
|
||||||
|
@ -364,7 +364,7 @@ pub mod reference_types {
|
|||||||
let global: &Global = instance.exports.get_global("global")?;
|
let global: &Global = instance.exports.get_global("global")?;
|
||||||
{
|
{
|
||||||
let er = ExternRef::new(&mut store, 3usize);
|
let er = ExternRef::new(&mut store, 3usize);
|
||||||
global.set(&mut store, Value::ExternRef(Some(er.clone())))?;
|
global.set(&mut store, Value::ExternRef(Some(er)))?;
|
||||||
}
|
}
|
||||||
let get_from_global: TypedFunction<(), Option<ExternRef>> = instance
|
let get_from_global: TypedFunction<(), Option<ExternRef>> = instance
|
||||||
.exports
|
.exports
|
||||||
@ -395,7 +395,7 @@ pub mod reference_types {
|
|||||||
|
|
||||||
let er = ExternRef::new(&mut store, 3usize);
|
let er = ExternRef::new(&mut store, 3usize);
|
||||||
|
|
||||||
let result = pass_extern_ref.call(&mut store, Some(er.clone()));
|
let result = pass_extern_ref.call(&mut store, Some(er));
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -439,7 +439,7 @@ pub mod reference_types {
|
|||||||
let result = grow_table_with_ref.call(&mut store, Some(er1.clone()), 10_000)?;
|
let result = grow_table_with_ref.call(&mut store, Some(er1.clone()), 10_000)?;
|
||||||
assert_eq!(result, -1);
|
assert_eq!(result, -1);
|
||||||
|
|
||||||
let result = grow_table_with_ref.call(&mut store, Some(er1.clone()), 8)?;
|
let result = grow_table_with_ref.call(&mut store, Some(er1), 8)?;
|
||||||
assert_eq!(result, 2);
|
assert_eq!(result, 2);
|
||||||
|
|
||||||
for i in 2..10 {
|
for i in 2..10 {
|
||||||
@ -451,7 +451,7 @@ pub mod reference_types {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
fill_table_with_ref.call(&mut store, Some(er2.clone()), 0, 2)?;
|
fill_table_with_ref.call(&mut store, Some(er2), 0, 2)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -459,7 +459,7 @@ pub mod reference_types {
|
|||||||
table2.set(&mut store, 1, Value::ExternRef(Some(er3.clone())))?;
|
table2.set(&mut store, 1, Value::ExternRef(Some(er3.clone())))?;
|
||||||
table2.set(&mut store, 2, Value::ExternRef(Some(er3.clone())))?;
|
table2.set(&mut store, 2, Value::ExternRef(Some(er3.clone())))?;
|
||||||
table2.set(&mut store, 3, Value::ExternRef(Some(er3.clone())))?;
|
table2.set(&mut store, 3, Value::ExternRef(Some(er3.clone())))?;
|
||||||
table2.set(&mut store, 4, Value::ExternRef(Some(er3.clone())))?;
|
table2.set(&mut store, 4, Value::ExternRef(Some(er3)))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -32,7 +32,7 @@ wasmer-middlewares = { version = "=3.1.0", path = "../middlewares", optional = t
|
|||||||
wasmer-wasi = { version = "=3.1.0", path = "../wasi", default-features = false, features = ["host-fs", "sys"], optional = true }
|
wasmer-wasi = { version = "=3.1.0", path = "../wasi", default-features = false, features = ["host-fs", "sys"], optional = true }
|
||||||
wasmer-types = { version = "=3.1.0", path = "../types" }
|
wasmer-types = { version = "=3.1.0", path = "../types" }
|
||||||
wasmer-vfs = { version = "=3.1.0", path = "../vfs", optional = true, default-features = false, features = ["static-fs"] }
|
wasmer-vfs = { version = "=3.1.0", path = "../vfs", optional = true, default-features = false, features = ["static-fs"] }
|
||||||
webc = { version = "0.4.1", optional = true }
|
webc = { version = "4.0.0", optional = true }
|
||||||
enumset = "1.0.2"
|
enumset = "1.0.2"
|
||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
|
8
lib/cache/src/cache.rs
vendored
8
lib/cache/src/cache.rs
vendored
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
use crate::hash::Hash;
|
use crate::hash::Hash;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use wasmer::{Module, Store};
|
use wasmer::{AsEngineRef, Module};
|
||||||
|
|
||||||
/// A generic cache for storing and loading compiled wasm modules.
|
/// A generic cache for storing and loading compiled wasm modules.
|
||||||
pub trait Cache {
|
pub trait Cache {
|
||||||
@ -17,7 +17,11 @@ pub trait Cache {
|
|||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// This function is unsafe as the cache store could be tampered with.
|
/// This function is unsafe as the cache store could be tampered with.
|
||||||
unsafe fn load(&self, store: &Store, key: Hash) -> Result<Module, Self::DeserializeError>;
|
unsafe fn load(
|
||||||
|
&self,
|
||||||
|
engine: &impl AsEngineRef,
|
||||||
|
key: Hash,
|
||||||
|
) -> Result<Module, Self::DeserializeError>;
|
||||||
|
|
||||||
/// Store a [`Module`] into the cache with the given [`Hash`].
|
/// Store a [`Module`] into the cache with the given [`Hash`].
|
||||||
fn store(&mut self, key: Hash, module: &Module) -> Result<(), Self::SerializeError>;
|
fn store(&mut self, key: Hash, module: &Module) -> Result<(), Self::SerializeError>;
|
||||||
|
10
lib/cache/src/filesystem.rs
vendored
10
lib/cache/src/filesystem.rs
vendored
@ -4,7 +4,7 @@ use crate::hash::Hash;
|
|||||||
use std::fs::{create_dir_all, File};
|
use std::fs::{create_dir_all, File};
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use wasmer::{DeserializeError, Module, SerializeError, Store};
|
use wasmer::{AsEngineRef, DeserializeError, Module, SerializeError};
|
||||||
|
|
||||||
/// Representation of a directory that contains compiled wasm artifacts.
|
/// Representation of a directory that contains compiled wasm artifacts.
|
||||||
///
|
///
|
||||||
@ -91,14 +91,18 @@ impl Cache for FileSystemCache {
|
|||||||
type DeserializeError = DeserializeError;
|
type DeserializeError = DeserializeError;
|
||||||
type SerializeError = SerializeError;
|
type SerializeError = SerializeError;
|
||||||
|
|
||||||
unsafe fn load(&self, store: &Store, key: Hash) -> Result<Module, Self::DeserializeError> {
|
unsafe fn load(
|
||||||
|
&self,
|
||||||
|
engine: &impl AsEngineRef,
|
||||||
|
key: Hash,
|
||||||
|
) -> Result<Module, Self::DeserializeError> {
|
||||||
let filename = if let Some(ref ext) = self.ext {
|
let filename = if let Some(ref ext) = self.ext {
|
||||||
format!("{}.{}", key.to_string(), ext)
|
format!("{}.{}", key.to_string(), ext)
|
||||||
} else {
|
} else {
|
||||||
key.to_string()
|
key.to_string()
|
||||||
};
|
};
|
||||||
let path = self.path.join(filename);
|
let path = self.path.join(filename);
|
||||||
let ret = Module::deserialize_from_file(store, path.clone());
|
let ret = Module::deserialize_from_file(engine, path.clone());
|
||||||
if ret.is_err() {
|
if ret.is_err() {
|
||||||
// If an error occurs while deserializing then we can not trust it anymore
|
// If an error occurs while deserializing then we can not trust it anymore
|
||||||
// so delete the cache file
|
// so delete the cache file
|
||||||
|
@ -37,9 +37,11 @@ wasmer-wasi-experimental-io-devices = { version = "=3.1.0", path = "../wasi-expe
|
|||||||
wasmer-wast = { version = "=3.1.0", path = "../../tests/lib/wast", optional = true }
|
wasmer-wast = { version = "=3.1.0", path = "../../tests/lib/wast", optional = true }
|
||||||
wasmer-cache = { version = "=3.1.0", path = "../cache", optional = true }
|
wasmer-cache = { version = "=3.1.0", path = "../cache", optional = true }
|
||||||
wasmer-types = { version = "=3.1.0", path = "../types" }
|
wasmer-types = { version = "=3.1.0", path = "../types" }
|
||||||
wasmer-registry = { version = "=3.1.0", path = "../registry" }
|
wasmer-registry = { version = "=4.0.0", path = "../registry" }
|
||||||
wasmer-object = { version = "=3.1.0", path = "../object", optional = true }
|
wasmer-object = { version = "=3.1.0", path = "../object", optional = true }
|
||||||
wasmer-vfs = { version = "=3.1.0", path = "../vfs", default-features = false, features = ["host-fs"] }
|
wasmer-vfs = { version = "=3.1.0", path = "../vfs", default-features = false, features = ["host-fs"] }
|
||||||
|
wasmer-wasm-interface = { version = "3.1.0", path = "../wasm-interface" }
|
||||||
|
wasmparser = "0.51.4"
|
||||||
atty = "0.2"
|
atty = "0.2"
|
||||||
colored = "2.0"
|
colored = "2.0"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
@ -52,7 +54,6 @@ bytesize = "1.0"
|
|||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
# For debug feature
|
# For debug feature
|
||||||
fern = { version = "0.6", features = ["colored"], optional = true }
|
fern = { version = "0.6", features = ["colored"], optional = true }
|
||||||
log = { version = "0.4", optional = true }
|
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
tempdir = "0.3.7"
|
tempdir = "0.3.7"
|
||||||
http_req = { version="^0.8", default-features = false, features = ["rust-tls"] }
|
http_req = { version="^0.8", default-features = false, features = ["rust-tls"] }
|
||||||
@ -62,18 +63,28 @@ dirs = { version = "4.0" }
|
|||||||
serde_json = { version = "1.0" }
|
serde_json = { version = "1.0" }
|
||||||
target-lexicon = { version = "0.12", features = ["std"] }
|
target-lexicon = { version = "0.12", features = ["std"] }
|
||||||
prettytable-rs = "0.9.0"
|
prettytable-rs = "0.9.0"
|
||||||
wapm-toml = "0.2.0"
|
wasmer-toml = "0.5.0"
|
||||||
walkdir = "2.3.2"
|
walkdir = "2.3.2"
|
||||||
regex = "1.6.0"
|
regex = "1.6.0"
|
||||||
toml = "0.5.9"
|
toml = "0.5.9"
|
||||||
url = "2.3.1"
|
url = "2.3.1"
|
||||||
libc = { version = "^0.2", default-features = false }
|
libc = { version = "^0.2", default-features = false }
|
||||||
nuke-dir = { version = "0.1.0", optional = true }
|
nuke-dir = { version = "0.1.0", optional = true }
|
||||||
webc = { version = "0.4.1", optional = true }
|
webc = { version = "4.0.0", optional = true }
|
||||||
isatty = "0.1.9"
|
isatty = "0.1.9"
|
||||||
dialoguer = "0.10.2"
|
dialoguer = "0.10.2"
|
||||||
tldextract = "0.6.0"
|
tldextract = "0.6.0"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
|
flate2 = "1.0.25"
|
||||||
|
cargo_metadata = "0.15.2"
|
||||||
|
rusqlite = { version = "0.28.0", features = ["bundled"] }
|
||||||
|
tar = "0.4.38"
|
||||||
|
thiserror = "1.0.37"
|
||||||
|
time = { version = "0.3.17", default-features = false, features = ["parsing", "std", "formatting"] }
|
||||||
|
log = "0.4.17"
|
||||||
|
minisign = "0.7.2"
|
||||||
|
semver = "1.0.14"
|
||||||
|
rpassword = "7.2.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
chrono = { version = "^0.4", default-features = false, features = [ "std", "clock" ] }
|
chrono = { version = "^0.4", default-features = false, features = [ "std", "clock" ] }
|
||||||
@ -146,7 +157,7 @@ llvm = [
|
|||||||
"wasmer-compiler-llvm",
|
"wasmer-compiler-llvm",
|
||||||
"compiler",
|
"compiler",
|
||||||
]
|
]
|
||||||
debug = ["fern", "log"]
|
debug = ["fern", "wasmer-wasi/logging"]
|
||||||
disable-all-logging = ["wasmer-wasi/disable-all-logging"]
|
disable-all-logging = ["wasmer-wasi/disable-all-logging"]
|
||||||
headless = []
|
headless = []
|
||||||
headless-minimal = ["headless", "disable-all-logging", "wasi"]
|
headless-minimal = ["headless", "disable-all-logging", "wasi"]
|
||||||
|
27
lib/cli/sql/migrations/0000.sql
Normal file
27
lib/cli/sql/migrations/0000.sql
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
CREATE TABLE personal_keys
|
||||||
|
(
|
||||||
|
id integer primary key,
|
||||||
|
active integer not null,
|
||||||
|
public_key_id text not null UNIQUE,
|
||||||
|
public_key_value text not null UNIQUE,
|
||||||
|
private_key_location text UNIQUE,
|
||||||
|
key_type_identifier text not null,
|
||||||
|
date_added text not null
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE wapm_users
|
||||||
|
(
|
||||||
|
id integer primary key,
|
||||||
|
name text not null UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE wapm_public_keys
|
||||||
|
(
|
||||||
|
id integer primary key,
|
||||||
|
public_key_id text not null UNIQUE,
|
||||||
|
user_key integer not null,
|
||||||
|
public_key_value text not null UNIQUE,
|
||||||
|
key_type_identifier text not null,
|
||||||
|
date_added text not null,
|
||||||
|
FOREIGN KEY(user_key) REFERENCES wapm_users(id)
|
||||||
|
);
|
9
lib/cli/sql/migrations/0001.sql
Normal file
9
lib/cli/sql/migrations/0001.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE wasm_contracts
|
||||||
|
(
|
||||||
|
id integer primary key,
|
||||||
|
contract_name text not null,
|
||||||
|
version text not null,
|
||||||
|
date_added text not null,
|
||||||
|
content text not null,
|
||||||
|
CONSTRAINT name_version_unique UNIQUE (contract_name, version)
|
||||||
|
);
|
26
lib/cli/sql/migrations/0002.sql
Normal file
26
lib/cli/sql/migrations/0002.sql
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
PRAGMA foreign_keys=off;
|
||||||
|
|
||||||
|
CREATE TABLE wasm_interfaces
|
||||||
|
(
|
||||||
|
id integer primary key,
|
||||||
|
interface_name text not null,
|
||||||
|
version text not null,
|
||||||
|
date_added text not null,
|
||||||
|
content text not null,
|
||||||
|
CONSTRAINT name_version_unique UNIQUE (interface_name, version)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO wasm_interfaces
|
||||||
|
(
|
||||||
|
id,
|
||||||
|
interface_name,
|
||||||
|
version,
|
||||||
|
date_added,
|
||||||
|
content
|
||||||
|
)
|
||||||
|
SELECT id, contract_name, version, date_added, content
|
||||||
|
FROM wasm_contracts;
|
||||||
|
|
||||||
|
DROP TABLE wasm_contracts;
|
||||||
|
|
||||||
|
PRAGMA foreign_keys=on;
|
@ -11,7 +11,7 @@ use crate::commands::CreateObj;
|
|||||||
#[cfg(feature = "wast")]
|
#[cfg(feature = "wast")]
|
||||||
use crate::commands::Wast;
|
use crate::commands::Wast;
|
||||||
use crate::commands::{
|
use crate::commands::{
|
||||||
Add, Cache, Config, Inspect, List, Login, Run, SelfUpdate, Validate, Whoami,
|
Add, Cache, Config, Init, Inspect, List, Login, Publish, Run, SelfUpdate, Validate, Whoami,
|
||||||
};
|
};
|
||||||
use crate::error::PrettyError;
|
use crate::error::PrettyError;
|
||||||
use clap::{CommandFactory, ErrorKind, Parser};
|
use clap::{CommandFactory, ErrorKind, Parser};
|
||||||
@ -46,6 +46,10 @@ enum WasmerCLIOptions {
|
|||||||
/// Login into a wapm.io-like registry
|
/// Login into a wapm.io-like registry
|
||||||
Login(Login),
|
Login(Login),
|
||||||
|
|
||||||
|
/// Login into a wapm.io-like registry
|
||||||
|
#[clap(name = "publish")]
|
||||||
|
Publish(Publish),
|
||||||
|
|
||||||
/// Wasmer cache
|
/// Wasmer cache
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
Cache(Cache),
|
Cache(Cache),
|
||||||
@ -135,6 +139,10 @@ enum WasmerCLIOptions {
|
|||||||
/// Inspect a WebAssembly file
|
/// Inspect a WebAssembly file
|
||||||
Inspect(Inspect),
|
Inspect(Inspect),
|
||||||
|
|
||||||
|
/// Initializes a new wasmer.toml file
|
||||||
|
#[clap(name = "init")]
|
||||||
|
Init(Init),
|
||||||
|
|
||||||
/// Run spec testsuite
|
/// Run spec testsuite
|
||||||
#[cfg(feature = "wast")]
|
#[cfg(feature = "wast")]
|
||||||
Wast(Wast),
|
Wast(Wast),
|
||||||
@ -165,8 +173,10 @@ impl WasmerCLIOptions {
|
|||||||
Self::CreateObj(create_obj) => create_obj.execute(),
|
Self::CreateObj(create_obj) => create_obj.execute(),
|
||||||
Self::Config(config) => config.execute(),
|
Self::Config(config) => config.execute(),
|
||||||
Self::Inspect(inspect) => inspect.execute(),
|
Self::Inspect(inspect) => inspect.execute(),
|
||||||
|
Self::Init(init) => init.execute(),
|
||||||
Self::List(list) => list.execute(),
|
Self::List(list) => list.execute(),
|
||||||
Self::Login(login) => login.execute(),
|
Self::Login(login) => login.execute(),
|
||||||
|
Self::Publish(publish) => publish.execute(),
|
||||||
#[cfg(feature = "wast")]
|
#[cfg(feature = "wast")]
|
||||||
Self::Wast(wast) => wast.execute(),
|
Self::Wast(wast) => wast.execute(),
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
@ -224,10 +234,9 @@ fn wasmer_main_inner() -> Result<(), anyhow::Error> {
|
|||||||
WasmerCLIOptions::Run(Run::from_binfmt_args())
|
WasmerCLIOptions::Run(Run::from_binfmt_args())
|
||||||
} else {
|
} else {
|
||||||
match command.unwrap_or(&"".to_string()).as_ref() {
|
match command.unwrap_or(&"".to_string()).as_ref() {
|
||||||
"add" | "cache" | "compile" | "config" | "create-exe" | "help" | "inspect" | "run"
|
"add" | "cache" | "compile" | "config" | "create-exe" | "help" | "inspect" | "init"
|
||||||
| "self-update" | "validate" | "wast" | "binfmt" | "list" | "login" => {
|
| "run" | "self-update" | "validate" | "wast" | "binfmt" | "list" | "login"
|
||||||
WasmerCLIOptions::parse()
|
| "publish" => WasmerCLIOptions::parse(),
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
WasmerCLIOptions::try_parse_from(args.iter()).unwrap_or_else(|e| {
|
WasmerCLIOptions::try_parse_from(args.iter()).unwrap_or_else(|e| {
|
||||||
match e.kind() {
|
match e.kind() {
|
||||||
|
@ -10,9 +10,11 @@ mod config;
|
|||||||
mod create_exe;
|
mod create_exe;
|
||||||
#[cfg(feature = "static-artifact-create")]
|
#[cfg(feature = "static-artifact-create")]
|
||||||
mod create_obj;
|
mod create_obj;
|
||||||
|
mod init;
|
||||||
mod inspect;
|
mod inspect;
|
||||||
mod list;
|
mod list;
|
||||||
mod login;
|
mod login;
|
||||||
|
mod publish;
|
||||||
mod run;
|
mod run;
|
||||||
mod self_update;
|
mod self_update;
|
||||||
mod validate;
|
mod validate;
|
||||||
@ -31,8 +33,8 @@ pub use create_obj::*;
|
|||||||
#[cfg(feature = "wast")]
|
#[cfg(feature = "wast")]
|
||||||
pub use wast::*;
|
pub use wast::*;
|
||||||
pub use {
|
pub use {
|
||||||
add::*, cache::*, config::*, inspect::*, list::*, login::*, run::*, self_update::*,
|
add::*, cache::*, config::*, init::*, inspect::*, list::*, login::*, publish::*, run::*,
|
||||||
validate::*, whoami::*,
|
self_update::*, validate::*, whoami::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The kind of object format to emit.
|
/// The kind of object format to emit.
|
||||||
|
@ -2,7 +2,7 @@ use std::process::{Command, Stdio};
|
|||||||
|
|
||||||
use anyhow::{Context, Error};
|
use anyhow::{Context, Error};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use wasmer_registry::{Bindings, PartialWapmConfig, ProgrammingLanguage};
|
use wasmer_registry::{Bindings, ProgrammingLanguage, WasmerConfig};
|
||||||
|
|
||||||
/// Add a WAPM package's bindings to your application.
|
/// Add a WAPM package's bindings to your application.
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
@ -76,7 +76,9 @@ impl Add {
|
|||||||
match &self.registry {
|
match &self.registry {
|
||||||
Some(r) => Ok(r.clone()),
|
Some(r) => Ok(r.clone()),
|
||||||
None => {
|
None => {
|
||||||
let cfg = PartialWapmConfig::from_file()
|
let wasmer_dir =
|
||||||
|
WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||||
|
let cfg = WasmerConfig::from_file(&wasmer_dir)
|
||||||
.map_err(Error::msg)
|
.map_err(Error::msg)
|
||||||
.context("Unable to load WAPM's config file")?;
|
.context("Unable to load WAPM's config file")?;
|
||||||
Ok(cfg.registry.get_current_registry())
|
Ok(cfg.registry.get_current_registry())
|
||||||
|
@ -3,10 +3,22 @@ use anyhow::{Context, Result};
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::str::ParseBoolError;
|
||||||
|
use wasmer_registry::WasmerConfig;
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
/// The options for the `wasmer config` subcommand
|
/// The options for the `wasmer config` subcommand: `wasmer config get --OPTION` or `wasmer config set [FLAG]`
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
#[clap(flatten)]
|
||||||
|
flags: Flags,
|
||||||
|
/// Subcommand for `wasmer config get | set`
|
||||||
|
#[clap(subcommand)]
|
||||||
|
set: Option<GetOrSet>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Normal configuration
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Flags {
|
||||||
/// Print the installation prefix.
|
/// Print the installation prefix.
|
||||||
#[clap(long, conflicts_with = "pkg-config")]
|
#[clap(long, conflicts_with = "pkg-config")]
|
||||||
prefix: bool,
|
prefix: bool,
|
||||||
@ -31,12 +43,118 @@ pub struct Config {
|
|||||||
#[clap(long, conflicts_with = "pkg-config")]
|
#[clap(long, conflicts_with = "pkg-config")]
|
||||||
cflags: bool,
|
cflags: bool,
|
||||||
|
|
||||||
/// It outputs the necessary details for compiling
|
/// Print the path to the wasmer configuration file where all settings are stored
|
||||||
|
#[clap(long, conflicts_with = "pkg-config")]
|
||||||
|
config_path: bool,
|
||||||
|
|
||||||
|
/// Outputs the necessary details for compiling
|
||||||
/// and linking a program to Wasmer, using the `pkg-config` format.
|
/// and linking a program to Wasmer, using the `pkg-config` format.
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pkg_config: bool,
|
pkg_config: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Subcommand for `wasmer config set`
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
|
||||||
|
pub enum GetOrSet {
|
||||||
|
/// `wasmer config get $KEY`
|
||||||
|
#[clap(subcommand)]
|
||||||
|
Get(RetrievableConfigField),
|
||||||
|
/// `wasmer config set $KEY $VALUE`
|
||||||
|
#[clap(subcommand)]
|
||||||
|
Set(StorableConfigField),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subcommand for `wasmer config get`
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
|
||||||
|
pub enum RetrievableConfigField {
|
||||||
|
/// Print the registry URL of the currently active registry
|
||||||
|
#[clap(name = "registry.url")]
|
||||||
|
RegistryUrl,
|
||||||
|
/// Print the token for the currently active registry or nothing if not logged in
|
||||||
|
#[clap(name = "registry.token")]
|
||||||
|
RegistryToken,
|
||||||
|
/// Print whether telemetry is currently enabled
|
||||||
|
#[clap(name = "telemetry.enabled")]
|
||||||
|
TelemetryEnabled,
|
||||||
|
/// Print whether update notifications are enabled
|
||||||
|
#[clap(name = "update-notifications.enabled")]
|
||||||
|
UpdateNotificationsEnabled,
|
||||||
|
/// Print the proxy URL
|
||||||
|
#[clap(name = "proxy.url")]
|
||||||
|
ProxyUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Setting that can be stored in the wasmer config
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
|
||||||
|
pub enum StorableConfigField {
|
||||||
|
/// Set the registry URL of the currently active registry
|
||||||
|
#[clap(name = "registry.url")]
|
||||||
|
RegistryUrl(SetRegistryUrl),
|
||||||
|
/// Set the token for the currently active registry or nothing if not logged in
|
||||||
|
#[clap(name = "registry.token")]
|
||||||
|
RegistryToken(SetRegistryToken),
|
||||||
|
/// Set whether telemetry is currently enabled
|
||||||
|
#[clap(name = "telemetry.enabled")]
|
||||||
|
TelemetryEnabled(SetTelemetryEnabled),
|
||||||
|
/// Set whether update notifications are enabled
|
||||||
|
#[clap(name = "update-notifications.enabled")]
|
||||||
|
UpdateNotificationsEnabled(SetUpdateNotificationsEnabled),
|
||||||
|
/// Set the active proxy URL
|
||||||
|
#[clap(name = "proxy.url")]
|
||||||
|
ProxyUrl(SetProxyUrl),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the current active registry URL
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
|
||||||
|
pub struct SetRegistryUrl {
|
||||||
|
/// Url of the registry
|
||||||
|
#[clap(name = "URL")]
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set or change the token for the current active registry
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
|
||||||
|
pub struct SetRegistryToken {
|
||||||
|
/// Token to set
|
||||||
|
#[clap(name = "TOKEN")]
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set if update notifications are enabled
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
|
||||||
|
pub struct SetUpdateNotificationsEnabled {
|
||||||
|
/// Whether to enable update notifications
|
||||||
|
#[clap(name = "ENABLED", possible_values = ["true", "false"])]
|
||||||
|
pub enabled: BoolString,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "true" or "false" for handling input in the CLI
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct BoolString(pub bool);
|
||||||
|
|
||||||
|
impl std::str::FromStr for BoolString {
|
||||||
|
type Err = ParseBoolError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(Self(bool::from_str(s)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set if telemetry is enabled
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
|
||||||
|
pub struct SetTelemetryEnabled {
|
||||||
|
/// Whether to enable telemetry
|
||||||
|
#[clap(name = "ENABLED", possible_values = ["true", "false"])]
|
||||||
|
pub enabled: BoolString,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set if a proxy URL should be used
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
|
||||||
|
pub struct SetProxyUrl {
|
||||||
|
/// Set if a proxy URL should be used (empty = unset proxy)
|
||||||
|
#[clap(name = "URL")]
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
/// Runs logic for the `config` subcommand
|
/// Runs logic for the `config` subcommand
|
||||||
pub fn execute(&self) -> Result<()> {
|
pub fn execute(&self) -> Result<()> {
|
||||||
@ -44,6 +162,12 @@ impl Config {
|
|||||||
.context("failed to retrieve the wasmer config".to_string())
|
.context("failed to retrieve the wasmer config".to_string())
|
||||||
}
|
}
|
||||||
fn inner_execute(&self) -> Result<()> {
|
fn inner_execute(&self) -> Result<()> {
|
||||||
|
if let Some(s) = self.set.as_ref() {
|
||||||
|
return s.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
let flags = &self.flags;
|
||||||
|
|
||||||
let key = "WASMER_DIR";
|
let key = "WASMER_DIR";
|
||||||
let wasmer_dir = env::var(key)
|
let wasmer_dir = env::var(key)
|
||||||
.or_else(|e| {
|
.or_else(|e| {
|
||||||
@ -65,7 +189,7 @@ impl Config {
|
|||||||
let cflags = format!("-I{}", includedir);
|
let cflags = format!("-I{}", includedir);
|
||||||
let libs = format!("-L{} -lwasmer", libdir);
|
let libs = format!("-L{} -lwasmer", libdir);
|
||||||
|
|
||||||
if self.pkg_config {
|
if flags.pkg_config {
|
||||||
println!("prefix={}", prefixdir);
|
println!("prefix={}", prefixdir);
|
||||||
println!("exec_prefix={}", bindir);
|
println!("exec_prefix={}", bindir);
|
||||||
println!("includedir={}", includedir);
|
println!("includedir={}", includedir);
|
||||||
@ -79,24 +203,114 @@ impl Config {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.prefix {
|
if flags.prefix {
|
||||||
println!("{}", prefixdir);
|
println!("{}", prefixdir);
|
||||||
}
|
}
|
||||||
if self.bindir {
|
if flags.bindir {
|
||||||
println!("{}", bindir);
|
println!("{}", bindir);
|
||||||
}
|
}
|
||||||
if self.includedir {
|
if flags.includedir {
|
||||||
println!("{}", includedir);
|
println!("{}", includedir);
|
||||||
}
|
}
|
||||||
if self.libdir {
|
if flags.libdir {
|
||||||
println!("{}", libdir);
|
println!("{}", libdir);
|
||||||
}
|
}
|
||||||
if self.libs {
|
if flags.libs {
|
||||||
println!("{}", libs);
|
println!("{}", libs);
|
||||||
}
|
}
|
||||||
if self.cflags {
|
if flags.cflags {
|
||||||
println!("{}", cflags);
|
println!("{}", cflags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flags.config_path {
|
||||||
|
let wasmer_dir = WasmerConfig::get_wasmer_dir()
|
||||||
|
.map_err(|e| anyhow::anyhow!("could not find wasmer dir: {e}"))?;
|
||||||
|
let path = WasmerConfig::get_file_location(&wasmer_dir);
|
||||||
|
println!("{}", path.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetOrSet {
|
||||||
|
fn execute(&self) -> Result<()> {
|
||||||
|
let wasmer_dir = WasmerConfig::get_wasmer_dir()
|
||||||
|
.map_err(|e| anyhow::anyhow!("could not find wasmer dir: {e}"))?;
|
||||||
|
let config_file = WasmerConfig::get_file_location(&wasmer_dir);
|
||||||
|
let mut config = WasmerConfig::from_file(&wasmer_dir).map_err(|e| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"could not find config file {e} at {}",
|
||||||
|
config_file.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
match self {
|
||||||
|
GetOrSet::Get(g) => match g {
|
||||||
|
RetrievableConfigField::RegistryUrl => {
|
||||||
|
println!("{}", config.registry.get_current_registry());
|
||||||
|
}
|
||||||
|
RetrievableConfigField::RegistryToken => {
|
||||||
|
if let Some(s) = config
|
||||||
|
.registry
|
||||||
|
.get_login_token_for_registry(&config.registry.get_current_registry())
|
||||||
|
{
|
||||||
|
println!("{s}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RetrievableConfigField::TelemetryEnabled => {
|
||||||
|
println!("{:?}", config.telemetry_enabled);
|
||||||
|
}
|
||||||
|
RetrievableConfigField::UpdateNotificationsEnabled => {
|
||||||
|
println!("{:?}", config.update_notifications_enabled);
|
||||||
|
}
|
||||||
|
RetrievableConfigField::ProxyUrl => {
|
||||||
|
if let Some(s) = config.proxy.url.as_ref() {
|
||||||
|
println!("{s}");
|
||||||
|
} else {
|
||||||
|
println!("none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GetOrSet::Set(s) => {
|
||||||
|
match s {
|
||||||
|
StorableConfigField::RegistryUrl(s) => {
|
||||||
|
config.registry.set_current_registry(&s.url);
|
||||||
|
let current_registry = config.registry.get_current_registry();
|
||||||
|
if let Some(u) = wasmer_registry::utils::get_username(¤t_registry)
|
||||||
|
.ok()
|
||||||
|
.and_then(|o| o)
|
||||||
|
{
|
||||||
|
println!(
|
||||||
|
"Successfully logged into registry {current_registry:?} as user {u:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StorableConfigField::RegistryToken(t) => {
|
||||||
|
config.registry.set_login_token_for_registry(
|
||||||
|
&config.registry.get_current_registry(),
|
||||||
|
&t.token,
|
||||||
|
wasmer_registry::config::UpdateRegistry::LeaveAsIs,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
StorableConfigField::TelemetryEnabled(t) => {
|
||||||
|
config.telemetry_enabled = t.enabled.0;
|
||||||
|
}
|
||||||
|
StorableConfigField::ProxyUrl(p) => {
|
||||||
|
if p.url == "none" || p.url.is_empty() {
|
||||||
|
config.proxy.url = None;
|
||||||
|
} else {
|
||||||
|
config.proxy.url = Some(p.url.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StorableConfigField::UpdateNotificationsEnabled(u) => {
|
||||||
|
config.update_notifications_enabled = u.enabled.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config
|
||||||
|
.save(config_file)
|
||||||
|
.with_context(|| anyhow::anyhow!("could not save config file"))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
517
lib/cli/src/commands/init.rs
Normal file
517
lib/cli/src/commands/init.rs
Normal file
@ -0,0 +1,517 @@
|
|||||||
|
use anyhow::Context;
|
||||||
|
use cargo_metadata::{CargoOpt, MetadataCommand};
|
||||||
|
use clap::Parser;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use wasmer_registry::WasmerConfig;
|
||||||
|
|
||||||
|
static NOTE: &str =
|
||||||
|
"# See more keys and definitions at https://docs.wasmer.io/ecosystem/wapm/manifest";
|
||||||
|
|
||||||
|
const NEWLINE: &str = if cfg!(windows) { "\r\n" } else { "\n" };
|
||||||
|
|
||||||
|
/// CLI args for the `wasmer init` command
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Init {
|
||||||
|
/// Initialize wasmer.toml for a library package
|
||||||
|
#[clap(long, group = "crate-type")]
|
||||||
|
pub lib: bool,
|
||||||
|
/// Initialize wasmer.toml for a binary package
|
||||||
|
#[clap(long, group = "crate-type")]
|
||||||
|
pub bin: bool,
|
||||||
|
/// Initialize an empty wasmer.toml
|
||||||
|
#[clap(long, group = "crate-type")]
|
||||||
|
pub empty: bool,
|
||||||
|
/// Force overwriting the wasmer.toml, even if it already exists
|
||||||
|
#[clap(long)]
|
||||||
|
pub overwrite: bool,
|
||||||
|
/// Don't display debug output
|
||||||
|
#[clap(long)]
|
||||||
|
pub quiet: bool,
|
||||||
|
/// Namespace to init with, default = current logged in user or _
|
||||||
|
#[clap(long)]
|
||||||
|
pub namespace: Option<String>,
|
||||||
|
/// Package name to init with, default = Cargo.toml name or current directory name
|
||||||
|
#[clap(long)]
|
||||||
|
pub package_name: Option<String>,
|
||||||
|
/// Version of the initialized package
|
||||||
|
#[clap(long)]
|
||||||
|
pub version: Option<semver::Version>,
|
||||||
|
/// If the `manifest-path` is a Cargo.toml, use that file to initialize the wasmer.toml
|
||||||
|
#[clap(long)]
|
||||||
|
pub manifest_path: Option<PathBuf>,
|
||||||
|
/// Add default dependencies for common packages
|
||||||
|
#[clap(long, value_enum)]
|
||||||
|
pub template: Option<Template>,
|
||||||
|
/// Include file paths into the target container filesystem
|
||||||
|
#[clap(long)]
|
||||||
|
pub include: Vec<String>,
|
||||||
|
/// Directory of the output file name. wasmer init will error if the target dir
|
||||||
|
/// already contains a wasmer.toml. Also sets the package name.
|
||||||
|
#[clap(name = "PACKAGE_PATH")]
|
||||||
|
pub out: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What template to use for the initialized wasmer.toml
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone, clap::ValueEnum)]
|
||||||
|
pub enum Template {
|
||||||
|
/// Add dependency on Python
|
||||||
|
Python,
|
||||||
|
/// Add dependency on JS
|
||||||
|
Js,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||||
|
enum BinOrLib {
|
||||||
|
Bin,
|
||||||
|
Lib,
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
// minimal version of the Cargo.toml [package] section
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct MiniCargoTomlPackage {
|
||||||
|
cargo_toml_path: PathBuf,
|
||||||
|
name: String,
|
||||||
|
version: semver::Version,
|
||||||
|
description: Option<String>,
|
||||||
|
homepage: Option<String>,
|
||||||
|
repository: Option<String>,
|
||||||
|
license: Option<String>,
|
||||||
|
readme: Option<PathBuf>,
|
||||||
|
license_file: Option<PathBuf>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
workspace_root: PathBuf,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
build_dir: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
static WASMER_TOML_NAME: &str = "wasmer.toml";
|
||||||
|
|
||||||
|
impl Init {
|
||||||
|
/// `wasmer init` execution
|
||||||
|
pub fn execute(&self) -> Result<(), anyhow::Error> {
|
||||||
|
let bin_or_lib = self.get_bin_or_lib()?;
|
||||||
|
|
||||||
|
// See if the directory has a Cargo.toml file, if yes, copy the license / readme, etc.
|
||||||
|
let manifest_path = match self.manifest_path.as_ref() {
|
||||||
|
Some(s) => s.clone(),
|
||||||
|
None => {
|
||||||
|
let cargo_toml_path = self
|
||||||
|
.out
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| std::env::current_dir().unwrap())
|
||||||
|
.join("Cargo.toml");
|
||||||
|
cargo_toml_path
|
||||||
|
.canonicalize()
|
||||||
|
.unwrap_or_else(|_| cargo_toml_path.clone())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let cargo_toml = if manifest_path.exists() {
|
||||||
|
parse_cargo_toml(&manifest_path).ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let (fallback_package_name, target_file) = self.target_file()?;
|
||||||
|
|
||||||
|
if target_file.exists() && !self.overwrite {
|
||||||
|
anyhow::bail!(
|
||||||
|
"wasmer project already initialized in {}",
|
||||||
|
target_file.display(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let constructed_manifest = construct_manifest(
|
||||||
|
cargo_toml.as_ref(),
|
||||||
|
&fallback_package_name,
|
||||||
|
self.package_name.as_deref(),
|
||||||
|
&target_file,
|
||||||
|
&manifest_path,
|
||||||
|
bin_or_lib,
|
||||||
|
self.namespace.clone(),
|
||||||
|
self.version.clone(),
|
||||||
|
self.template.as_ref(),
|
||||||
|
self.include.as_slice(),
|
||||||
|
self.quiet,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(parent) = target_file.parent() {
|
||||||
|
let _ = std::fs::create_dir_all(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate the wasmer.toml and exit
|
||||||
|
Self::write_wasmer_toml(&target_file, &constructed_manifest)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the metadata to a wasmer.toml file
|
||||||
|
fn write_wasmer_toml(
|
||||||
|
path: &PathBuf,
|
||||||
|
toml: &wasmer_toml::Manifest,
|
||||||
|
) -> Result<(), anyhow::Error> {
|
||||||
|
let toml_string = toml::to_string_pretty(&toml)?
|
||||||
|
.replace(
|
||||||
|
"[dependencies]",
|
||||||
|
&format!("{NOTE}{NEWLINE}{NEWLINE}[dependencies]"),
|
||||||
|
)
|
||||||
|
.lines()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(NEWLINE);
|
||||||
|
|
||||||
|
std::fs::write(&path, &toml_string)
|
||||||
|
.with_context(|| format!("Unable to write to \"{}\"", path.display()))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn target_file(&self) -> Result<(String, PathBuf), anyhow::Error> {
|
||||||
|
match self.out.as_ref() {
|
||||||
|
None => {
|
||||||
|
let current_dir = std::env::current_dir()?;
|
||||||
|
let package_name = self
|
||||||
|
.package_name
|
||||||
|
.clone()
|
||||||
|
.or_else(|| {
|
||||||
|
current_dir
|
||||||
|
.canonicalize()
|
||||||
|
.ok()?
|
||||||
|
.file_stem()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
})
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("no current dir name"))?;
|
||||||
|
Ok((package_name, current_dir.join(WASMER_TOML_NAME)))
|
||||||
|
}
|
||||||
|
Some(s) => {
|
||||||
|
std::fs::create_dir_all(s)
|
||||||
|
.map_err(|e| anyhow::anyhow!("{e}"))
|
||||||
|
.with_context(|| anyhow::anyhow!("{}", s.display()))?;
|
||||||
|
let package_name = self
|
||||||
|
.package_name
|
||||||
|
.clone()
|
||||||
|
.or_else(|| {
|
||||||
|
s.canonicalize()
|
||||||
|
.ok()?
|
||||||
|
.file_stem()
|
||||||
|
.and_then(|s| s.to_str())
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
})
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("no dir name"))?;
|
||||||
|
Ok((package_name, s.join(WASMER_TOML_NAME)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_filesystem_mapping(include: &[String]) -> Option<HashMap<String, PathBuf>> {
|
||||||
|
if include.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(
|
||||||
|
include
|
||||||
|
.iter()
|
||||||
|
.map(|path| {
|
||||||
|
if path == "." || path == "/" {
|
||||||
|
return ("/".to_string(), Path::new("/").to_path_buf());
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = format!("./{path}");
|
||||||
|
let value = PathBuf::from(format!("/{path}"));
|
||||||
|
|
||||||
|
(key, value)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_command(
|
||||||
|
modules: &[wasmer_toml::Module],
|
||||||
|
bin_or_lib: BinOrLib,
|
||||||
|
) -> Option<Vec<wasmer_toml::Command>> {
|
||||||
|
match bin_or_lib {
|
||||||
|
BinOrLib::Bin => Some(
|
||||||
|
modules
|
||||||
|
.iter()
|
||||||
|
.map(|m| {
|
||||||
|
wasmer_toml::Command::V1(wasmer_toml::CommandV1 {
|
||||||
|
name: m.name.clone(),
|
||||||
|
module: m.name.clone(),
|
||||||
|
main_args: None,
|
||||||
|
package: None,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
BinOrLib::Lib | BinOrLib::Empty => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the dependencies based on the `--template` flag
|
||||||
|
fn get_dependencies(template: Option<&Template>) -> HashMap<String, String> {
|
||||||
|
let mut map = HashMap::default();
|
||||||
|
|
||||||
|
match template {
|
||||||
|
Some(Template::Js) => {
|
||||||
|
map.insert("quickjs".to_string(), "quickjs/quickjs@latest".to_string());
|
||||||
|
}
|
||||||
|
Some(Template::Python) => {
|
||||||
|
map.insert("python".to_string(), "python/python@latest".to_string());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns whether the template for the wasmer.toml should be a binary, a library or an empty file
|
||||||
|
fn get_bin_or_lib(&self) -> Result<BinOrLib, anyhow::Error> {
|
||||||
|
match (self.empty, self.bin, self.lib) {
|
||||||
|
(true, false, false) => Ok(BinOrLib::Empty),
|
||||||
|
(false, true, false) => Ok(BinOrLib::Bin),
|
||||||
|
(false, false, true) => Ok(BinOrLib::Lib),
|
||||||
|
(false, false, false) => Ok(BinOrLib::Bin),
|
||||||
|
_ => anyhow::bail!("Only one of --bin, --lib, or --empty can be provided"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get bindings returns the first .wai / .wit file found and
|
||||||
|
/// optionally takes a warning callback that is triggered when > 1 .wai files are found
|
||||||
|
fn get_bindings(target_file: &Path, bin_or_lib: BinOrLib) -> Option<GetBindingsResult> {
|
||||||
|
match bin_or_lib {
|
||||||
|
BinOrLib::Bin | BinOrLib::Empty => None,
|
||||||
|
BinOrLib::Lib => target_file.parent().and_then(|parent| {
|
||||||
|
let all_bindings = walkdir::WalkDir::new(parent)
|
||||||
|
.min_depth(1)
|
||||||
|
.max_depth(3)
|
||||||
|
.follow_links(false)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.filter_map(|e| {
|
||||||
|
let is_wit = e.path().extension().and_then(|s| s.to_str()) == Some(".wit");
|
||||||
|
let is_wai = e.path().extension().and_then(|s| s.to_str()) == Some(".wai");
|
||||||
|
if is_wit {
|
||||||
|
Some(wasmer_toml::Bindings::Wit(wasmer_toml::WitBindings {
|
||||||
|
wit_exports: e.path().to_path_buf(),
|
||||||
|
wit_bindgen: semver::Version::parse("0.1.0").unwrap(),
|
||||||
|
}))
|
||||||
|
} else if is_wai {
|
||||||
|
Some(wasmer_toml::Bindings::Wai(wasmer_toml::WaiBindings {
|
||||||
|
exports: None,
|
||||||
|
imports: vec![e.path().to_path_buf()],
|
||||||
|
wai_version: semver::Version::parse("0.2.0").unwrap(),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if all_bindings.is_empty() {
|
||||||
|
None
|
||||||
|
} else if all_bindings.len() == 1 {
|
||||||
|
Some(GetBindingsResult::OneBinding(all_bindings[0].clone()))
|
||||||
|
} else {
|
||||||
|
Some(GetBindingsResult::MultiBindings(all_bindings))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum GetBindingsResult {
|
||||||
|
OneBinding(wasmer_toml::Bindings),
|
||||||
|
MultiBindings(Vec<wasmer_toml::Bindings>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetBindingsResult {
|
||||||
|
fn first_binding(&self) -> Option<wasmer_toml::Bindings> {
|
||||||
|
match self {
|
||||||
|
Self::OneBinding(s) => Some(s.clone()),
|
||||||
|
Self::MultiBindings(s) => s.get(0).cloned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn construct_manifest(
|
||||||
|
cargo_toml: Option<&MiniCargoTomlPackage>,
|
||||||
|
fallback_package_name: &String,
|
||||||
|
package_name: Option<&str>,
|
||||||
|
target_file: &Path,
|
||||||
|
manifest_path: &Path,
|
||||||
|
bin_or_lib: BinOrLib,
|
||||||
|
namespace: Option<String>,
|
||||||
|
version: Option<semver::Version>,
|
||||||
|
template: Option<&Template>,
|
||||||
|
include_fs: &[String],
|
||||||
|
quiet: bool,
|
||||||
|
) -> Result<wasmer_toml::Manifest, anyhow::Error> {
|
||||||
|
if let Some(ct) = cargo_toml.as_ref() {
|
||||||
|
let msg = format!(
|
||||||
|
"NOTE: Initializing wasmer.toml file with metadata from Cargo.toml{NEWLINE} -> {}",
|
||||||
|
ct.cargo_toml_path.display()
|
||||||
|
);
|
||||||
|
if !quiet {
|
||||||
|
println!("{msg}");
|
||||||
|
}
|
||||||
|
log::warn!("{msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let package_name = package_name.unwrap_or_else(|| {
|
||||||
|
cargo_toml
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| &p.name)
|
||||||
|
.unwrap_or(fallback_package_name)
|
||||||
|
});
|
||||||
|
let wasmer_dir = WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||||
|
let namespace = namespace.or_else(|| {
|
||||||
|
wasmer_registry::whoami(&wasmer_dir, None, None)
|
||||||
|
.ok()
|
||||||
|
.map(|o| o.1)
|
||||||
|
});
|
||||||
|
let version = version.unwrap_or_else(|| {
|
||||||
|
cargo_toml
|
||||||
|
.as_ref()
|
||||||
|
.map(|t| t.version.clone())
|
||||||
|
.unwrap_or_else(|| semver::Version::parse("0.1.0").unwrap())
|
||||||
|
});
|
||||||
|
let license = cargo_toml.as_ref().and_then(|t| t.license.clone());
|
||||||
|
let license_file = cargo_toml.as_ref().and_then(|t| t.license_file.clone());
|
||||||
|
let readme = cargo_toml.as_ref().and_then(|t| t.readme.clone());
|
||||||
|
let repository = cargo_toml.as_ref().and_then(|t| t.repository.clone());
|
||||||
|
let homepage = cargo_toml.as_ref().and_then(|t| t.homepage.clone());
|
||||||
|
let description = cargo_toml
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|t| t.description.clone())
|
||||||
|
.unwrap_or_else(|| format!("Description for package {package_name}"));
|
||||||
|
|
||||||
|
let default_abi = wasmer_toml::Abi::Wasi;
|
||||||
|
let bindings = Init::get_bindings(target_file, bin_or_lib);
|
||||||
|
|
||||||
|
if let Some(GetBindingsResult::MultiBindings(m)) = bindings.as_ref() {
|
||||||
|
let found = m
|
||||||
|
.iter()
|
||||||
|
.map(|m| match m {
|
||||||
|
wasmer_toml::Bindings::Wit(wb) => {
|
||||||
|
format!("found: {}", serde_json::to_string(wb).unwrap_or_default())
|
||||||
|
}
|
||||||
|
wasmer_toml::Bindings::Wai(wb) => {
|
||||||
|
format!("found: {}", serde_json::to_string(wb).unwrap_or_default())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\r\n");
|
||||||
|
|
||||||
|
let msg = vec![
|
||||||
|
String::new(),
|
||||||
|
" It looks like your project contains multiple *.wai files.".to_string(),
|
||||||
|
" Make sure you update the [[module.bindings]] appropriately".to_string(),
|
||||||
|
String::new(),
|
||||||
|
found,
|
||||||
|
];
|
||||||
|
let msg = msg.join("\r\n");
|
||||||
|
if !quiet {
|
||||||
|
println!("{msg}");
|
||||||
|
}
|
||||||
|
log::warn!("{msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let modules = vec![wasmer_toml::Module {
|
||||||
|
name: package_name.to_string(),
|
||||||
|
source: cargo_toml
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| {
|
||||||
|
// Normalize the path to /target/release to be relative to the parent of the Cargo.toml
|
||||||
|
let outpath = p
|
||||||
|
.build_dir
|
||||||
|
.join("release")
|
||||||
|
.join(&format!("{package_name}.wasm"));
|
||||||
|
let canonicalized_outpath = outpath.canonicalize().unwrap_or(outpath);
|
||||||
|
let outpath_str = format!("{}", canonicalized_outpath.display());
|
||||||
|
let manifest_canonicalized = manifest_path
|
||||||
|
.parent()
|
||||||
|
.and_then(|p| p.canonicalize().ok())
|
||||||
|
.unwrap_or_else(|| manifest_path.to_path_buf());
|
||||||
|
let manifest_str = format!("{}/", manifest_canonicalized.display());
|
||||||
|
let relative_str = outpath_str.replacen(&manifest_str, "", 1);
|
||||||
|
Path::new(&relative_str).to_path_buf()
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| Path::new(&format!("{package_name}.wasm")).to_path_buf()),
|
||||||
|
kind: None,
|
||||||
|
abi: default_abi,
|
||||||
|
bindings: bindings.as_ref().and_then(|b| b.first_binding()),
|
||||||
|
interfaces: Some({
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert("wasi".to_string(), "0.1.0-unstable".to_string());
|
||||||
|
map
|
||||||
|
}),
|
||||||
|
}];
|
||||||
|
|
||||||
|
Ok(wasmer_toml::Manifest {
|
||||||
|
package: wasmer_toml::Package {
|
||||||
|
name: if let Some(s) = namespace {
|
||||||
|
format!("{s}/{package_name}")
|
||||||
|
} else {
|
||||||
|
package_name.to_string()
|
||||||
|
},
|
||||||
|
version,
|
||||||
|
description,
|
||||||
|
license,
|
||||||
|
license_file,
|
||||||
|
readme,
|
||||||
|
repository,
|
||||||
|
homepage,
|
||||||
|
wasmer_extra_flags: None,
|
||||||
|
disable_command_rename: false,
|
||||||
|
rename_commands_to_raw_command_name: false,
|
||||||
|
},
|
||||||
|
dependencies: Some(Init::get_dependencies(template)),
|
||||||
|
command: Init::get_command(&modules, bin_or_lib),
|
||||||
|
module: match bin_or_lib {
|
||||||
|
BinOrLib::Empty => None,
|
||||||
|
_ => Some(modules),
|
||||||
|
},
|
||||||
|
fs: Init::get_filesystem_mapping(include_fs),
|
||||||
|
base_directory_path: target_file
|
||||||
|
.parent()
|
||||||
|
.map(|o| o.to_path_buf())
|
||||||
|
.unwrap_or_else(|| target_file.to_path_buf()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn parse_cargo_toml(manifest_path: &PathBuf) -> Result<MiniCargoTomlPackage, anyhow::Error> {
|
||||||
|
let mut metadata = MetadataCommand::new();
|
||||||
|
metadata.manifest_path(&manifest_path);
|
||||||
|
metadata.no_deps();
|
||||||
|
metadata.features(CargoOpt::AllFeatures);
|
||||||
|
|
||||||
|
let metadata = metadata.exec().with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Unable to load metadata from \"{}\"",
|
||||||
|
manifest_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let package = metadata
|
||||||
|
.root_package()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("no root package found in cargo metadata"))
|
||||||
|
.context(anyhow::anyhow!("{}", manifest_path.display()))?;
|
||||||
|
|
||||||
|
Ok(MiniCargoTomlPackage {
|
||||||
|
cargo_toml_path: manifest_path.clone(),
|
||||||
|
name: package.name.clone(),
|
||||||
|
version: package.version.clone(),
|
||||||
|
description: package.description.clone(),
|
||||||
|
homepage: package.homepage.clone(),
|
||||||
|
repository: package.repository.clone(),
|
||||||
|
license: package.license.clone(),
|
||||||
|
readme: package.readme.clone().map(|s| s.into_std_path_buf()),
|
||||||
|
license_file: package.license_file.clone().map(|f| f.into_std_path_buf()),
|
||||||
|
workspace_root: metadata.workspace_root.into_std_path_buf(),
|
||||||
|
build_dir: metadata
|
||||||
|
.target_directory
|
||||||
|
.into_std_path_buf()
|
||||||
|
.join("wasm32-wasi"),
|
||||||
|
})
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use wasmer_registry::WasmerConfig;
|
||||||
|
|
||||||
/// Subcommand for listing packages
|
/// Subcommand for listing packages
|
||||||
#[derive(Debug, Copy, Clone, Parser)]
|
#[derive(Debug, Copy, Clone, Parser)]
|
||||||
@ -8,8 +9,9 @@ impl List {
|
|||||||
/// execute [List]
|
/// execute [List]
|
||||||
pub fn execute(&self) -> Result<(), anyhow::Error> {
|
pub fn execute(&self) -> Result<(), anyhow::Error> {
|
||||||
use prettytable::{format, row, Table};
|
use prettytable::{format, row, Table};
|
||||||
|
let wasmer_dir =
|
||||||
let rows = wasmer_registry::get_all_local_packages()
|
WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?;
|
||||||
|
let rows = wasmer_registry::get_all_local_packages(&wasmer_dir)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|pkg| {
|
.filter_map(|pkg| {
|
||||||
let package_root_path = pkg.path;
|
let package_root_path = pkg.path;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
use dialoguer::Input;
|
use dialoguer::Input;
|
||||||
|
use wasmer_registry::WasmerConfig;
|
||||||
|
|
||||||
/// Subcommand for listing packages
|
/// Subcommand for listing packages
|
||||||
#[derive(Debug, Clone, Parser)]
|
#[derive(Debug, Clone, Parser)]
|
||||||
@ -51,7 +52,9 @@ impl Login {
|
|||||||
/// execute [List]
|
/// execute [List]
|
||||||
pub fn execute(&self) -> Result<(), anyhow::Error> {
|
pub fn execute(&self) -> Result<(), anyhow::Error> {
|
||||||
let token = self.get_token_or_ask_user()?;
|
let token = self.get_token_or_ask_user()?;
|
||||||
match wasmer_registry::login::login_and_save_token(&self.registry, &token)? {
|
let wasmer_dir =
|
||||||
|
WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?;
|
||||||
|
match wasmer_registry::login::login_and_save_token(&wasmer_dir, &self.registry, &token)? {
|
||||||
Some(s) => println!("Login for WAPM user {:?} saved", s),
|
Some(s) => println!("Login for WAPM user {:?} saved", s),
|
||||||
None => println!(
|
None => println!(
|
||||||
"Error: no user found on registry {:?} with token {:?}. Token saved regardless.",
|
"Error: no user found on registry {:?} with token {:?}. Token saved regardless.",
|
||||||
|
659
lib/cli/src/commands/publish.rs
Normal file
659
lib/cli/src/commands/publish.rs
Normal file
@ -0,0 +1,659 @@
|
|||||||
|
use anyhow::Context;
|
||||||
|
use clap::Parser;
|
||||||
|
use flate2::{write::GzEncoder, Compression};
|
||||||
|
use rusqlite::{params, Connection, OpenFlags, TransactionBehavior};
|
||||||
|
use std::fs;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use tar::Builder;
|
||||||
|
use thiserror::Error;
|
||||||
|
use time::{self, OffsetDateTime};
|
||||||
|
use wasmer_registry::publish::SignArchiveResult;
|
||||||
|
use wasmer_registry::{WasmerConfig, PACKAGE_TOML_FALLBACK_NAME};
|
||||||
|
|
||||||
|
const MIGRATIONS: &[(i32, &str)] = &[
|
||||||
|
(0, include_str!("../../sql/migrations/0000.sql")),
|
||||||
|
(1, include_str!("../../sql/migrations/0001.sql")),
|
||||||
|
(2, include_str!("../../sql/migrations/0002.sql")),
|
||||||
|
];
|
||||||
|
|
||||||
|
const CURRENT_DATA_VERSION: usize = MIGRATIONS.len();
|
||||||
|
|
||||||
|
/// CLI options for the `wasmer publish` command
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct Publish {
|
||||||
|
/// Registry to publish to
|
||||||
|
#[clap(long)]
|
||||||
|
pub registry: Option<String>,
|
||||||
|
/// Run the publish logic without sending anything to the registry server
|
||||||
|
#[clap(long, name = "dry-run")]
|
||||||
|
pub dry_run: bool,
|
||||||
|
/// Run the publish command without any output
|
||||||
|
#[clap(long)]
|
||||||
|
pub quiet: bool,
|
||||||
|
/// Override the package of the uploaded package in the wasmer.toml
|
||||||
|
#[clap(long)]
|
||||||
|
pub package_name: Option<String>,
|
||||||
|
/// Override the package version of the uploaded package in the wasmer.toml
|
||||||
|
#[clap(long)]
|
||||||
|
pub version: Option<semver::Version>,
|
||||||
|
/// Override the token (by default, it will use the current logged in user)
|
||||||
|
#[clap(long)]
|
||||||
|
pub token: Option<String>,
|
||||||
|
/// Skip validation of the uploaded package
|
||||||
|
#[clap(long)]
|
||||||
|
pub no_validate: bool,
|
||||||
|
/// Directory containing the `wasmer.toml` (defaults to current root dir)
|
||||||
|
#[clap(name = "PACKAGE_PATH")]
|
||||||
|
pub package_path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
enum PublishError {
|
||||||
|
#[error("Cannot publish without a module.")]
|
||||||
|
NoModule,
|
||||||
|
#[error("Unable to publish the \"{module}\" module because \"{}\" is not a file", path.display())]
|
||||||
|
SourceMustBeFile { module: String, path: PathBuf },
|
||||||
|
#[error("Unable to load the bindings for \"{module}\" because \"{}\" doesn't exist", path.display())]
|
||||||
|
MissingBindings { module: String, path: PathBuf },
|
||||||
|
#[error("Error building package when parsing module \"{0}\": {1}.")]
|
||||||
|
ErrorBuildingPackage(String, io::Error),
|
||||||
|
#[error(
|
||||||
|
"Path \"{0}\", specified in the manifest as part of the package file system does not exist.",
|
||||||
|
)]
|
||||||
|
MissingManifestFsPath(String),
|
||||||
|
#[error("When processing the package filesystem, found path \"{0}\" which is not a directory")]
|
||||||
|
PackageFileSystemEntryMustBeDirectory(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Publish {
|
||||||
|
/// Executes `wasmer publish`
|
||||||
|
pub fn execute(&self) -> Result<(), anyhow::Error> {
|
||||||
|
let mut builder = Builder::new(Vec::new());
|
||||||
|
|
||||||
|
let cwd = match self.package_path.as_ref() {
|
||||||
|
Some(s) => std::env::current_dir()?.join(s),
|
||||||
|
None => std::env::current_dir()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let manifest_path_buf = cwd.join("wasmer.toml");
|
||||||
|
let manifest = std::fs::read_to_string(&manifest_path_buf)
|
||||||
|
.map_err(|e| anyhow::anyhow!("could not find manifest: {e}"))
|
||||||
|
.with_context(|| anyhow::anyhow!("{}", manifest_path_buf.display()))?;
|
||||||
|
let mut manifest = wasmer_toml::Manifest::parse(&manifest)?;
|
||||||
|
manifest.base_directory_path = cwd.clone();
|
||||||
|
|
||||||
|
if let Some(package_name) = self.package_name.as_ref() {
|
||||||
|
manifest.package.name = package_name.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(version) = self.version.as_ref() {
|
||||||
|
manifest.package.version = version.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let registry = match self.registry.as_deref() {
|
||||||
|
Some(s) => wasmer_registry::format_graphql(s),
|
||||||
|
None => {
|
||||||
|
let wasmer_dir = WasmerConfig::get_wasmer_dir()
|
||||||
|
.map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?;
|
||||||
|
let config = WasmerConfig::from_file(&wasmer_dir)
|
||||||
|
.map_err(|e| anyhow::anyhow!("could not load config {e}"))?;
|
||||||
|
config.registry.get_current_registry()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !self.no_validate {
|
||||||
|
validate::validate_directory(&manifest, ®istry, cwd.clone())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append_path_with_name(&manifest_path_buf, PACKAGE_TOML_FALLBACK_NAME)?;
|
||||||
|
|
||||||
|
let manifest_string = toml::to_string(&manifest)?;
|
||||||
|
|
||||||
|
let (readme, license) = construct_tar_gz(&mut builder, &manifest, &cwd)?;
|
||||||
|
|
||||||
|
builder
|
||||||
|
.finish()
|
||||||
|
.map_err(|e| anyhow::anyhow!("failed to finish .tar.gz builder: {e}"))?;
|
||||||
|
let tar_archive_data = builder.into_inner().map_err(|_| PublishError::NoModule)?;
|
||||||
|
let archive_name = "package.tar.gz".to_string();
|
||||||
|
let archive_dir = tempfile::TempDir::new()?;
|
||||||
|
let archive_dir_path: &std::path::Path = archive_dir.as_ref();
|
||||||
|
fs::create_dir(archive_dir_path.join("wapm_package"))?;
|
||||||
|
let archive_path = archive_dir_path.join("wapm_package").join(&archive_name);
|
||||||
|
let mut compressed_archive = fs::File::create(&archive_path).unwrap();
|
||||||
|
let mut gz_enc = GzEncoder::new(&mut compressed_archive, Compression::best());
|
||||||
|
|
||||||
|
gz_enc.write_all(&tar_archive_data).unwrap();
|
||||||
|
let _compressed_archive = gz_enc.finish().unwrap();
|
||||||
|
let mut compressed_archive_reader = fs::File::open(&archive_path)?;
|
||||||
|
|
||||||
|
let maybe_signature_data = sign_compressed_archive(&mut compressed_archive_reader)?;
|
||||||
|
let archived_data_size = archive_path.metadata()?.len();
|
||||||
|
|
||||||
|
assert!(archive_path.exists());
|
||||||
|
assert!(archive_path.is_file());
|
||||||
|
|
||||||
|
if self.dry_run {
|
||||||
|
// dry run: publish is done here
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Successfully published package `{}@{}`",
|
||||||
|
manifest.package.name, manifest.package.version
|
||||||
|
);
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"Publish succeeded, but package was not published because it was run in dry-run mode"
|
||||||
|
);
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
wasmer_registry::publish::try_chunked_uploading(
|
||||||
|
Some(registry),
|
||||||
|
self.token.clone(),
|
||||||
|
&manifest.package,
|
||||||
|
&manifest_string,
|
||||||
|
&license,
|
||||||
|
&readme,
|
||||||
|
&archive_name,
|
||||||
|
&archive_path,
|
||||||
|
&maybe_signature_data,
|
||||||
|
archived_data_size,
|
||||||
|
self.quiet,
|
||||||
|
)
|
||||||
|
.map_err(on_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn construct_tar_gz(
|
||||||
|
builder: &mut tar::Builder<Vec<u8>>,
|
||||||
|
manifest: &wasmer_toml::Manifest,
|
||||||
|
cwd: &Path,
|
||||||
|
) -> Result<(Option<String>, Option<String>), anyhow::Error> {
|
||||||
|
let package = &manifest.package;
|
||||||
|
let modules = manifest.module.as_ref().ok_or(PublishError::NoModule)?;
|
||||||
|
|
||||||
|
let readme = match package.readme.as_ref() {
|
||||||
|
None => None,
|
||||||
|
Some(s) => {
|
||||||
|
let path = append_path_to_tar_gz(builder, &manifest.base_directory_path, s).map_err(
|
||||||
|
|(p, e)| PublishError::ErrorBuildingPackage(format!("{}", p.display()), e),
|
||||||
|
)?;
|
||||||
|
fs::read_to_string(path).ok()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let license_file = match package.license_file.as_ref() {
|
||||||
|
None => None,
|
||||||
|
Some(s) => {
|
||||||
|
let path = append_path_to_tar_gz(builder, &manifest.base_directory_path, s).map_err(
|
||||||
|
|(p, e)| PublishError::ErrorBuildingPackage(format!("{}", p.display()), e),
|
||||||
|
)?;
|
||||||
|
fs::read_to_string(path).ok()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for module in modules {
|
||||||
|
append_path_to_tar_gz(builder, &manifest.base_directory_path, &module.source).map_err(
|
||||||
|
|(normalized_path, _)| PublishError::SourceMustBeFile {
|
||||||
|
module: module.name.clone(),
|
||||||
|
path: normalized_path,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if let Some(bindings) = &module.bindings {
|
||||||
|
for path in bindings.referenced_files(&manifest.base_directory_path)? {
|
||||||
|
append_path_to_tar_gz(builder, &manifest.base_directory_path, &path).map_err(
|
||||||
|
|(normalized_path, _)| PublishError::MissingBindings {
|
||||||
|
module: module.name.clone(),
|
||||||
|
path: normalized_path,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bundle the package filesystem
|
||||||
|
let default = std::collections::HashMap::default();
|
||||||
|
for (_alias, path) in manifest.fs.as_ref().unwrap_or(&default).iter() {
|
||||||
|
let normalized_path = normalize_path(cwd, path);
|
||||||
|
let path_metadata = normalized_path.metadata().map_err(|_| {
|
||||||
|
PublishError::MissingManifestFsPath(normalized_path.to_string_lossy().to_string())
|
||||||
|
})?;
|
||||||
|
if path_metadata.is_dir() {
|
||||||
|
builder.append_dir_all(path, &normalized_path)
|
||||||
|
} else {
|
||||||
|
return Err(PublishError::PackageFileSystemEntryMustBeDirectory(
|
||||||
|
path.to_string_lossy().to_string(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
.map_err(|_| {
|
||||||
|
PublishError::MissingManifestFsPath(normalized_path.to_string_lossy().to_string())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((readme, license_file))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_path_to_tar_gz(
|
||||||
|
builder: &mut tar::Builder<Vec<u8>>,
|
||||||
|
base_path: &Path,
|
||||||
|
target_path: &Path,
|
||||||
|
) -> Result<PathBuf, (PathBuf, io::Error)> {
|
||||||
|
let normalized_path = normalize_path(base_path, target_path);
|
||||||
|
normalized_path
|
||||||
|
.metadata()
|
||||||
|
.map_err(|e| (normalized_path.clone(), e))?;
|
||||||
|
builder
|
||||||
|
.append_path_with_name(&normalized_path, &target_path)
|
||||||
|
.map_err(|e| (normalized_path.clone(), e))?;
|
||||||
|
Ok(normalized_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_error(e: anyhow::Error) -> anyhow::Error {
|
||||||
|
#[cfg(feature = "telemetry")]
|
||||||
|
sentry::integrations::anyhow::capture_anyhow(&e);
|
||||||
|
|
||||||
|
e
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_path(cwd: &Path, path: &Path) -> PathBuf {
|
||||||
|
let mut out = PathBuf::from(cwd);
|
||||||
|
let mut components = path.components();
|
||||||
|
if path.is_absolute() {
|
||||||
|
log::warn!(
|
||||||
|
"Interpreting absolute path {} as a relative path",
|
||||||
|
path.to_string_lossy()
|
||||||
|
);
|
||||||
|
components.next();
|
||||||
|
}
|
||||||
|
for comp in components {
|
||||||
|
out.push(comp);
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes the package archive as a File and attempts to sign it using the active key
|
||||||
|
/// returns the public key id used to sign it and the signature string itself
|
||||||
|
pub fn sign_compressed_archive(
|
||||||
|
compressed_archive: &mut fs::File,
|
||||||
|
) -> anyhow::Result<SignArchiveResult> {
|
||||||
|
let key_db = open_db()?;
|
||||||
|
let personal_key = if let Ok(v) = get_active_personal_key(&key_db) {
|
||||||
|
v
|
||||||
|
} else {
|
||||||
|
return Ok(SignArchiveResult::NoKeyRegistered);
|
||||||
|
};
|
||||||
|
let password = rpassword::prompt_password(format!(
|
||||||
|
"Please enter your password for the key pair {}:",
|
||||||
|
&personal_key.public_key_id
|
||||||
|
))
|
||||||
|
.ok();
|
||||||
|
let private_key = if let Some(priv_key_location) = personal_key.private_key_location {
|
||||||
|
match minisign::SecretKey::from_file(&priv_key_location, password) {
|
||||||
|
Ok(priv_key_data) => priv_key_data,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(
|
||||||
|
"Could not read private key from location {}: {}",
|
||||||
|
priv_key_location,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: add more info about why this might have happened and what the user can do about it
|
||||||
|
log::warn!("Active key does not have a private key location registered with it!");
|
||||||
|
return Err(anyhow!("Cannot sign package, no private key"));
|
||||||
|
};
|
||||||
|
let public_key = minisign::PublicKey::from_base64(&personal_key.public_key_value)?;
|
||||||
|
let signature = minisign::sign(
|
||||||
|
Some(&public_key),
|
||||||
|
&private_key,
|
||||||
|
compressed_archive,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
Ok(SignArchiveResult::Ok {
|
||||||
|
public_key_id: personal_key.public_key_id,
|
||||||
|
signature: signature.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens an exclusive read/write connection to the database, creating it if it does not exist
|
||||||
|
pub fn open_db() -> anyhow::Result<Connection> {
|
||||||
|
let wasmer_dir =
|
||||||
|
WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?;
|
||||||
|
let db_path = WasmerConfig::get_database_file_path(&wasmer_dir);
|
||||||
|
let mut conn = Connection::open_with_flags(
|
||||||
|
db_path,
|
||||||
|
OpenFlags::SQLITE_OPEN_CREATE
|
||||||
|
| OpenFlags::SQLITE_OPEN_READ_WRITE
|
||||||
|
| OpenFlags::SQLITE_OPEN_FULL_MUTEX,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
apply_migrations(&mut conn)?;
|
||||||
|
Ok(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies migrations to the database
|
||||||
|
pub fn apply_migrations(conn: &mut Connection) -> anyhow::Result<()> {
|
||||||
|
let user_version = conn.pragma_query_value(None, "user_version", |val| val.get(0))?;
|
||||||
|
for data_version in user_version..CURRENT_DATA_VERSION {
|
||||||
|
log::debug!("Applying migration {}", data_version);
|
||||||
|
apply_migration(conn, data_version as i32)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
enum MigrationError {
|
||||||
|
#[error(
|
||||||
|
"Critical internal error: the data version {0} is not handleded; current data version: {1}"
|
||||||
|
)]
|
||||||
|
MigrationNumberDoesNotExist(i32, i32),
|
||||||
|
#[error("Critical internal error: failed to commit trasaction migrating to data version {0}")]
|
||||||
|
CommitFailed(i32),
|
||||||
|
#[error("Critical internal error: transaction failed on migration number {0}: {1}")]
|
||||||
|
TransactionFailed(i32, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies migrations to the database and updates the `user_version` pragma.
|
||||||
|
/// Every migration must leave the database in a valid state.
|
||||||
|
fn apply_migration(conn: &mut Connection, migration_number: i32) -> Result<(), MigrationError> {
|
||||||
|
let tx = conn
|
||||||
|
.transaction_with_behavior(TransactionBehavior::Immediate)
|
||||||
|
.map_err(|e| MigrationError::TransactionFailed(migration_number, format!("{}", e)))?;
|
||||||
|
|
||||||
|
let migration_to_apply = MIGRATIONS
|
||||||
|
.iter()
|
||||||
|
.find_map(|(number, sql)| {
|
||||||
|
if *number == migration_number {
|
||||||
|
Some(sql)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok_or({
|
||||||
|
MigrationError::MigrationNumberDoesNotExist(
|
||||||
|
migration_number,
|
||||||
|
CURRENT_DATA_VERSION as i32,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
tx.execute_batch(migration_to_apply)
|
||||||
|
.map_err(|e| MigrationError::TransactionFailed(migration_number, format!("{}", e)))?;
|
||||||
|
|
||||||
|
tx.pragma_update(None, "user_version", &(migration_number + 1))
|
||||||
|
.map_err(|e| MigrationError::TransactionFailed(migration_number, format!("{}", e)))?;
|
||||||
|
tx.commit()
|
||||||
|
.map_err(|_| MigrationError::CommitFailed(migration_number))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about one of the user's keys
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PersonalKey {
|
||||||
|
/// Flag saying if the key will be used (there can only be one active key at a time)
|
||||||
|
pub active: bool,
|
||||||
|
/// The public key's tag. Used to identify the key pair
|
||||||
|
pub public_key_id: String,
|
||||||
|
/// The raw value of the public key in base64
|
||||||
|
pub public_key_value: String,
|
||||||
|
/// The location in the file system of the private key
|
||||||
|
pub private_key_location: Option<String>,
|
||||||
|
/// The type of private/public key this is
|
||||||
|
pub key_type_identifier: String,
|
||||||
|
/// The time at which the key was registered with wapm
|
||||||
|
pub date_created: OffsetDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_active_personal_key(conn: &Connection) -> anyhow::Result<PersonalKey> {
|
||||||
|
let mut stmt = conn.prepare(
|
||||||
|
"SELECT active, public_key_value, private_key_location, date_added, key_type_identifier, public_key_id FROM personal_keys
|
||||||
|
where active = 1",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let result = stmt
|
||||||
|
.query_map(params![], |row| {
|
||||||
|
Ok(PersonalKey {
|
||||||
|
active: row.get(0)?,
|
||||||
|
public_key_value: row.get(1)?,
|
||||||
|
private_key_location: row.get(2)?,
|
||||||
|
date_created: {
|
||||||
|
use time::format_description::well_known::Rfc3339;
|
||||||
|
let time_str: String = row.get(3)?;
|
||||||
|
OffsetDateTime::parse(&time_str, &Rfc3339)
|
||||||
|
.unwrap_or_else(|_| panic!("Failed to parse time string {}", &time_str))
|
||||||
|
},
|
||||||
|
key_type_identifier: row.get(4)?,
|
||||||
|
public_key_id: row.get(5)?,
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
.next();
|
||||||
|
|
||||||
|
if let Some(res) = result {
|
||||||
|
Ok(res?)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("No active key found"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod interfaces {
|
||||||
|
|
||||||
|
use rusqlite::{params, Connection, TransactionBehavior};
|
||||||
|
|
||||||
|
pub const WASM_INTERFACE_EXISTENCE_CHECK: &str =
|
||||||
|
include_str!("./sql/wasm_interface_existence_check.sql");
|
||||||
|
pub const INSERT_WASM_INTERFACE: &str = include_str!("./sql/insert_interface.sql");
|
||||||
|
pub const GET_WASM_INTERFACE: &str = include_str!("./sql/get_interface.sql");
|
||||||
|
|
||||||
|
pub fn interface_exists(
|
||||||
|
conn: &mut Connection,
|
||||||
|
interface_name: &str,
|
||||||
|
version: &str,
|
||||||
|
) -> anyhow::Result<bool> {
|
||||||
|
let mut stmt = conn.prepare(WASM_INTERFACE_EXISTENCE_CHECK)?;
|
||||||
|
Ok(stmt.exists(params![interface_name, version])?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_interface_from_db(
|
||||||
|
conn: &mut Connection,
|
||||||
|
interface_name: &str,
|
||||||
|
version: &str,
|
||||||
|
) -> anyhow::Result<wasmer_wasm_interface::Interface> {
|
||||||
|
let mut stmt = conn.prepare(GET_WASM_INTERFACE)?;
|
||||||
|
let interface_string: String =
|
||||||
|
stmt.query_row(params![interface_name, version], |row| row.get(0))?;
|
||||||
|
|
||||||
|
wasmer_wasm_interface::parser::parse_interface(&interface_string).map_err(|e| {
|
||||||
|
anyhow!(
|
||||||
|
"Failed to parse interface {} version {} in database: {}",
|
||||||
|
interface_name,
|
||||||
|
version,
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn import_interface(
|
||||||
|
conn: &mut Connection,
|
||||||
|
interface_name: &str,
|
||||||
|
version: &str,
|
||||||
|
content: &str,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
// fail if we already have this interface
|
||||||
|
{
|
||||||
|
let mut key_check = conn.prepare(WASM_INTERFACE_EXISTENCE_CHECK)?;
|
||||||
|
let result = key_check.exists(params![interface_name, version])?;
|
||||||
|
|
||||||
|
if result {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Interface {}, version {} already exists",
|
||||||
|
interface_name,
|
||||||
|
version
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
|
||||||
|
let time_string = get_current_time_in_format().expect("Could not get current time");
|
||||||
|
|
||||||
|
log::debug!("Adding interface {:?} {:?}", interface_name, version);
|
||||||
|
tx.execute(
|
||||||
|
INSERT_WASM_INTERFACE,
|
||||||
|
params![interface_name, version, time_string, content],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
tx.commit()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the current time in our standard format
|
||||||
|
pub fn get_current_time_in_format() -> Option<String> {
|
||||||
|
use time::format_description::well_known::Rfc3339;
|
||||||
|
let cur_time = time::OffsetDateTime::now_utc();
|
||||||
|
cur_time.format(&Rfc3339).ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod validate {
|
||||||
|
use super::interfaces;
|
||||||
|
use std::{
|
||||||
|
fs,
|
||||||
|
io::Read,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
use wasmer_registry::interface::InterfaceFromServer;
|
||||||
|
use wasmer_wasm_interface::{validate, Interface};
|
||||||
|
|
||||||
|
pub fn validate_directory(
|
||||||
|
manifest: &wasmer_toml::Manifest,
|
||||||
|
registry: &str,
|
||||||
|
pkg_path: PathBuf,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
// validate as dir
|
||||||
|
if let Some(modules) = manifest.module.as_ref() {
|
||||||
|
for module in modules.iter() {
|
||||||
|
let source_path = if module.source.is_relative() {
|
||||||
|
manifest.base_directory_path.join(&module.source)
|
||||||
|
} else {
|
||||||
|
module.source.clone()
|
||||||
|
};
|
||||||
|
let source_path_string = source_path.to_string_lossy().to_string();
|
||||||
|
let mut wasm_file =
|
||||||
|
fs::File::open(&source_path).map_err(|_| ValidationError::MissingFile {
|
||||||
|
file: source_path_string.clone(),
|
||||||
|
})?;
|
||||||
|
let mut wasm_buffer = Vec::new();
|
||||||
|
wasm_file.read_to_end(&mut wasm_buffer).map_err(|err| {
|
||||||
|
ValidationError::MiscCannotRead {
|
||||||
|
file: source_path_string.clone(),
|
||||||
|
error: format!("{}", err),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Some(bindings) = &module.bindings {
|
||||||
|
validate_bindings(bindings, &manifest.base_directory_path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hack, short circuit if no interface for now
|
||||||
|
if module.interfaces.is_none() {
|
||||||
|
return validate_wasm_and_report_errors_old(
|
||||||
|
&wasm_buffer[..],
|
||||||
|
source_path_string,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut conn = super::open_db()?;
|
||||||
|
let mut interface: Interface = Default::default();
|
||||||
|
for (interface_name, interface_version) in
|
||||||
|
module.interfaces.clone().unwrap_or_default().into_iter()
|
||||||
|
{
|
||||||
|
if !interfaces::interface_exists(
|
||||||
|
&mut conn,
|
||||||
|
&interface_name,
|
||||||
|
&interface_version,
|
||||||
|
)? {
|
||||||
|
// download interface and store it if we don't have it locally
|
||||||
|
let interface_data_from_server = InterfaceFromServer::get(
|
||||||
|
registry,
|
||||||
|
interface_name.clone(),
|
||||||
|
interface_version.clone(),
|
||||||
|
)?;
|
||||||
|
interfaces::import_interface(
|
||||||
|
&mut conn,
|
||||||
|
&interface_name,
|
||||||
|
&interface_version,
|
||||||
|
&interface_data_from_server.content,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
let sub_interface = interfaces::load_interface_from_db(
|
||||||
|
&mut conn,
|
||||||
|
&interface_name,
|
||||||
|
&interface_version,
|
||||||
|
)?;
|
||||||
|
interface = interface.merge(sub_interface).map_err(|e| {
|
||||||
|
anyhow!("Failed to merge interface {}: {}", &interface_name, e)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
validate::validate_wasm_and_report_errors(&wasm_buffer, &interface).map_err(
|
||||||
|
|e| ValidationError::InvalidWasm {
|
||||||
|
file: source_path_string,
|
||||||
|
error: format!("{:?}", e),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::debug!("package at path {:#?} validated", &pkg_path);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_bindings(
|
||||||
|
bindings: &wasmer_toml::Bindings,
|
||||||
|
base_directory_path: &Path,
|
||||||
|
) -> Result<(), ValidationError> {
|
||||||
|
// Note: checking for referenced files will make sure they all exist.
|
||||||
|
let _ = bindings.referenced_files(base_directory_path)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ValidationError {
|
||||||
|
#[error("WASM file \"{file}\" detected as invalid because {error}")]
|
||||||
|
InvalidWasm { file: String, error: String },
|
||||||
|
#[error("Could not find file {file}")]
|
||||||
|
MissingFile { file: String },
|
||||||
|
#[error("Failed to read file {file}; {error}")]
|
||||||
|
MiscCannotRead { file: String, error: String },
|
||||||
|
#[error(transparent)]
|
||||||
|
Imports(#[from] wasmer_toml::ImportsError),
|
||||||
|
}
|
||||||
|
|
||||||
|
// legacy function, validates wasm. TODO: clean up
|
||||||
|
pub fn validate_wasm_and_report_errors_old(
|
||||||
|
wasm: &[u8],
|
||||||
|
file_name: String,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
use wasmparser::WasmDecoder;
|
||||||
|
let mut parser = wasmparser::ValidatingParser::new(wasm, None);
|
||||||
|
loop {
|
||||||
|
let state = parser.read();
|
||||||
|
match state {
|
||||||
|
wasmparser::ParserState::EndWasm => return Ok(()),
|
||||||
|
wasmparser::ParserState::Error(e) => {
|
||||||
|
return Err(ValidationError::InvalidWasm {
|
||||||
|
file: file_name,
|
||||||
|
error: format!("{}", e),
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
lib/cli/src/commands/sql/get_interface.sql
Normal file
4
lib/cli/src/commands/sql/get_interface.sql
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
SELECT content
|
||||||
|
FROM wasm_interfaces
|
||||||
|
WHERE interface_name = (?1)
|
||||||
|
AND version = (?2)
|
3
lib/cli/src/commands/sql/insert_interface.sql
Normal file
3
lib/cli/src/commands/sql/insert_interface.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
INSERT INTO wasm_interfaces
|
||||||
|
(interface_name, version, date_added, content)
|
||||||
|
VALUES (?1, ?2, ?3, ?4)
|
@ -0,0 +1,5 @@
|
|||||||
|
SELECT 1
|
||||||
|
FROM wasm_interfaces
|
||||||
|
WHERE interface_name = (?1)
|
||||||
|
AND version = (?2)
|
||||||
|
LIMIT 1
|
@ -1,4 +1,5 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use wasmer_registry::WasmerConfig;
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
/// The options for the `wasmer whoami` subcommand
|
/// The options for the `wasmer whoami` subcommand
|
||||||
@ -11,7 +12,10 @@ pub struct Whoami {
|
|||||||
impl Whoami {
|
impl Whoami {
|
||||||
/// Execute `wasmer whoami`
|
/// Execute `wasmer whoami`
|
||||||
pub fn execute(&self) -> Result<(), anyhow::Error> {
|
pub fn execute(&self) -> Result<(), anyhow::Error> {
|
||||||
let (registry, username) = wasmer_registry::whoami(self.registry.as_deref())?;
|
let wasmer_dir =
|
||||||
|
WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?;
|
||||||
|
let (registry, username) =
|
||||||
|
wasmer_registry::whoami(&wasmer_dir, self.registry.as_deref(), None)?;
|
||||||
println!("logged into registry {registry:?} as user {username:?}");
|
println!("logged into registry {registry:?} as user {username:?}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ use anyhow::Context;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
use wasmer_registry::WasmerConfig;
|
||||||
|
|
||||||
/// Source of a package
|
/// Source of a package
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
@ -47,7 +48,7 @@ impl PackageSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Downloads the package (if any) to the installation directory, returns the path
|
/// Downloads the package (if any) to the installation directory, returns the path
|
||||||
/// of the package directory (containing the wapm.toml)
|
/// of the package directory (containing the wasmer.toml)
|
||||||
pub fn download_and_get_filepath(&self) -> Result<PathBuf, anyhow::Error> {
|
pub fn download_and_get_filepath(&self) -> Result<PathBuf, anyhow::Error> {
|
||||||
let url = match self {
|
let url = match self {
|
||||||
Self::File(f) => {
|
Self::File(f) => {
|
||||||
@ -61,20 +62,28 @@ impl PackageSource {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
Self::Url(u) => {
|
Self::Url(u) => {
|
||||||
if let Some(path) = wasmer_registry::Package::is_url_already_installed(u) {
|
let wasmer_dir = WasmerConfig::get_wasmer_dir()
|
||||||
|
.map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?;
|
||||||
|
if let Some(path) =
|
||||||
|
wasmer_registry::Package::is_url_already_installed(u, &wasmer_dir)
|
||||||
|
{
|
||||||
return Ok(path);
|
return Ok(path);
|
||||||
} else {
|
} else {
|
||||||
u.clone()
|
u.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Package(p) => {
|
Self::Package(p) => {
|
||||||
|
let wasmer_dir = WasmerConfig::get_wasmer_dir()
|
||||||
|
.map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?;
|
||||||
let package_path = Path::new(&p.file()).to_path_buf();
|
let package_path = Path::new(&p.file()).to_path_buf();
|
||||||
if package_path.exists() {
|
if package_path.exists() {
|
||||||
return Ok(package_path);
|
return Ok(package_path);
|
||||||
} else if let Some(path) = p.already_installed() {
|
} else if let Some(path) = p.already_installed(&wasmer_dir) {
|
||||||
return Ok(path);
|
return Ok(path);
|
||||||
} else {
|
} else {
|
||||||
p.url()?
|
let config = WasmerConfig::from_file(&wasmer_dir)
|
||||||
|
.map_err(|e| anyhow::anyhow!("error loading wasmer config file: {e}"))?;
|
||||||
|
p.url(&config.registry.get_current_registry())?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -85,8 +94,10 @@ impl PackageSource {
|
|||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let wasmer_dir =
|
||||||
|
WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?;
|
||||||
let mut sp = start_spinner(format!("Installing package {url} ..."));
|
let mut sp = start_spinner(format!("Installing package {url} ..."));
|
||||||
let opt_path = wasmer_registry::install_package(&url);
|
let opt_path = wasmer_registry::install_package(&wasmer_dir, &url);
|
||||||
if let Some(sp) = sp.take() {
|
if let Some(sp) = sp.take() {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
sp.clear();
|
sp.clear();
|
||||||
|
@ -38,4 +38,3 @@ wasm = ["std", "unwind"]
|
|||||||
unwind = ["cranelift-codegen/unwind", "gimli"]
|
unwind = ["cranelift-codegen/unwind", "gimli"]
|
||||||
std = ["cranelift-codegen/std", "cranelift-frontend/std", "wasmer-compiler/std", "wasmer-types/std"]
|
std = ["cranelift-codegen/std", "cranelift-frontend/std", "wasmer-compiler/std", "wasmer-types/std"]
|
||||||
core = ["hashbrown", "cranelift-codegen/core", "cranelift-frontend/core"]
|
core = ["hashbrown", "cranelift-codegen/core", "cranelift-frontend/core"]
|
||||||
verbose = []
|
|
||||||
|
@ -79,8 +79,7 @@ impl FuncTranslator {
|
|||||||
environ: &mut FE,
|
environ: &mut FE,
|
||||||
) -> WasmResult<()> {
|
) -> WasmResult<()> {
|
||||||
let _tt = timing::wasm_translate_function();
|
let _tt = timing::wasm_translate_function();
|
||||||
#[cfg(feature = "verbose")]
|
tracing::trace!(
|
||||||
tracing::info!(
|
|
||||||
"translate({} bytes, {}{})",
|
"translate({} bytes, {}{})",
|
||||||
reader.bytes_remaining(),
|
reader.bytes_remaining(),
|
||||||
func.name,
|
func.name,
|
||||||
|
@ -60,7 +60,10 @@ impl UnwindRegistry {
|
|||||||
|
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
#[allow(clippy::cast_ptr_alignment)]
|
||||||
unsafe fn register_frames(&mut self, eh_frame: &[u8]) {
|
unsafe fn register_frames(&mut self, eh_frame: &[u8]) {
|
||||||
if cfg!(all(target_os = "linux", target_env = "gnu")) {
|
if cfg!(any(
|
||||||
|
all(target_os = "linux", target_env = "gnu"),
|
||||||
|
target_os = "freebsd"
|
||||||
|
)) {
|
||||||
// Registering an empty `eh_frame` (i.e. which
|
// Registering an empty `eh_frame` (i.e. which
|
||||||
// contains empty FDEs) cause problems on Linux when
|
// contains empty FDEs) cause problems on Linux when
|
||||||
// deregistering it. We must avoid this
|
// deregistering it. We must avoid this
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "wasmer-registry"
|
name = "wasmer-registry"
|
||||||
version = "3.1.0"
|
version = "4.0.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Crate to interact with the wasmer registry (wapm.io), download packages, etc."
|
description = "Crate to interact with the wasmer registry (wapm.io), download packages, etc."
|
||||||
@ -20,12 +20,12 @@ serde_json = "1.0.85"
|
|||||||
url = "2.3.1"
|
url = "2.3.1"
|
||||||
thiserror = "1.0.37"
|
thiserror = "1.0.37"
|
||||||
toml = "0.5.9"
|
toml = "0.5.9"
|
||||||
wapm-toml = "0.2.0"
|
wasmer-toml = "0.5.0"
|
||||||
tar = "0.4.38"
|
tar = "0.4.38"
|
||||||
flate2 = "1.0.24"
|
flate2 = "1.0.24"
|
||||||
semver = "1.0.14"
|
semver = "1.0.14"
|
||||||
lzma-rs = "0.2.0"
|
lzma-rs = "0.2.0"
|
||||||
webc = { version ="0.4.1", features = ["mmap"] }
|
webc = { version ="4.0.0", features = ["mmap"] }
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
tokio = "1.21.2"
|
tokio = "1.21.2"
|
||||||
tempdir = "0.3.7"
|
tempdir = "0.3.7"
|
||||||
@ -34,3 +34,6 @@ regex = "1.7.0"
|
|||||||
fs_extra = "1.2.0"
|
fs_extra = "1.2.0"
|
||||||
filetime = "0.2.19"
|
filetime = "0.2.19"
|
||||||
tldextract = "0.6.0"
|
tldextract = "0.6.0"
|
||||||
|
console = "0.15.2"
|
||||||
|
indicatif = "0.17.2"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
query GetInterfaceVersionQuery ($name: String!, $version: String!) {
|
||||||
|
interface: getInterfaceVersion(name: $name, version: $version) {
|
||||||
|
version,
|
||||||
|
content,
|
||||||
|
interface {
|
||||||
|
name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
lib/registry/graphql/queries/get_signed_url.graphql
Normal file
5
lib/registry/graphql/queries/get_signed_url.graphql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
query GetSignedUrl ($name:String!, $version:String!,$expiresAfterSeconds:Int) {
|
||||||
|
url: getSignedUrlForPackageUpload(name:$name, version:$version,expiresAfterSeconds:$expiresAfterSeconds) {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
22
lib/registry/graphql/queries/publish_package_chunked.graphql
Normal file
22
lib/registry/graphql/queries/publish_package_chunked.graphql
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
mutation PublishPackageMutationChunked($name: String!, $version: String!, $description: String!, $manifest: String!, $license: String, $licenseFile: String, $readme: String, $fileName:String, $repository:String, $homepage:String, $signature: InputSignature, $signedUrl:String) {
|
||||||
|
publishPackage(input: {
|
||||||
|
name: $name,
|
||||||
|
version: $version,
|
||||||
|
description: $description,
|
||||||
|
manifest: $manifest,
|
||||||
|
license: $license,
|
||||||
|
licenseFile: $licenseFile,
|
||||||
|
readme: $readme,
|
||||||
|
file: $fileName,
|
||||||
|
signedUrl: $signedUrl,
|
||||||
|
repository: $repository,
|
||||||
|
homepage: $homepage,
|
||||||
|
signature: $signature,
|
||||||
|
clientMutationId: ""
|
||||||
|
}) {
|
||||||
|
success
|
||||||
|
packageVersion {
|
||||||
|
version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +1,21 @@
|
|||||||
use graphql_client::GraphQLQuery;
|
use graphql_client::GraphQLQuery;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use serde::Serialize;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
pub static GLOBAL_CONFIG_DATABASE_FILE_NAME: &str = "wasmer.sqlite";
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Serialize, Debug, PartialEq, Eq)]
|
#[derive(Deserialize, Default, Serialize, Debug, PartialEq, Eq)]
|
||||||
pub struct PartialWapmConfig {
|
pub struct WasmerConfig {
|
||||||
/// The number of seconds to wait before checking the registry for a new
|
|
||||||
/// version of the package.
|
|
||||||
#[serde(default = "wax_default_cooldown")]
|
|
||||||
pub wax_cooldown: i32,
|
|
||||||
|
|
||||||
/// The registry that wapm will connect to.
|
|
||||||
pub registry: Registries,
|
|
||||||
|
|
||||||
/// Whether or not telemetry is enabled.
|
/// Whether or not telemetry is enabled.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub telemetry: Telemetry,
|
pub telemetry_enabled: bool,
|
||||||
|
|
||||||
/// Whether or not updated notifications are enabled.
|
/// Whether or not updated notifications are enabled.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub update_notifications: UpdateNotifications,
|
pub update_notifications_enabled: bool,
|
||||||
|
|
||||||
|
/// The registry that wapm will connect to.
|
||||||
|
pub registry: MultiRegistry,
|
||||||
|
|
||||||
/// The proxy to use when connecting to the Internet.
|
/// The proxy to use when connecting to the Internet.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@ -36,38 +31,31 @@ pub struct Proxy {
|
|||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Default)]
|
/// Struct to store login tokens for multiple registry URLs
|
||||||
pub struct UpdateNotifications {
|
/// inside of the wasmer.toml configuration file
|
||||||
pub enabled: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Default)]
|
|
||||||
pub struct Telemetry {
|
|
||||||
pub enabled: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum Registries {
|
|
||||||
Single(Registry),
|
|
||||||
Multi(MultiRegistry),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
|
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct MultiRegistry {
|
pub struct MultiRegistry {
|
||||||
/// Currently active registry
|
/// Currently active registry
|
||||||
pub current: String,
|
pub active_registry: String,
|
||||||
/// Map from "RegistryUrl" to "LoginToken", in order to
|
/// Map from "RegistryUrl" to "LoginToken", in order to
|
||||||
/// be able to be able to easily switch between registries
|
/// be able to be able to easily switch between registries
|
||||||
pub tokens: BTreeMap<String, String>,
|
pub tokens: Vec<RegistryLogin>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Registries {
|
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct RegistryLogin {
|
||||||
|
/// Registry URL to login to
|
||||||
|
pub registry: String,
|
||||||
|
/// Login token for the registry
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MultiRegistry {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Registries::Single(Registry {
|
MultiRegistry {
|
||||||
url: format_graphql("https://registry.wapm.io"),
|
active_registry: format_graphql("wapm.io"),
|
||||||
token: None,
|
tokens: Vec::new(),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +98,7 @@ pub enum UpdateRegistry {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_registries_switch_token() {
|
fn test_registries_switch_token() {
|
||||||
let mut registries = Registries::default();
|
let mut registries = MultiRegistry::default();
|
||||||
|
|
||||||
registries.set_current_registry("https://registry.wapm.dev");
|
registries.set_current_registry("https://registry.wapm.dev");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -142,18 +130,12 @@ fn test_registries_switch_token() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Registries {
|
impl MultiRegistry {
|
||||||
/// Gets the current (active) registry URL
|
/// Gets the current (active) registry URL
|
||||||
pub fn clear_current_registry_token(&mut self) {
|
pub fn clear_current_registry_token(&mut self) {
|
||||||
match self {
|
self.tokens.retain(|i| i.registry != self.active_registry);
|
||||||
Registries::Single(s) => {
|
self.tokens
|
||||||
s.token = None;
|
.retain(|i| i.registry != format_graphql(&self.active_registry));
|
||||||
}
|
|
||||||
Registries::Multi(m) => {
|
|
||||||
m.tokens.remove(&m.current);
|
|
||||||
m.tokens.remove(&format_graphql(&m.current));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_graphql_url(&self) -> String {
|
pub fn get_graphql_url(&self) -> String {
|
||||||
@ -163,10 +145,7 @@ impl Registries {
|
|||||||
|
|
||||||
/// Gets the current (active) registry URL
|
/// Gets the current (active) registry URL
|
||||||
pub fn get_current_registry(&self) -> String {
|
pub fn get_current_registry(&self) -> String {
|
||||||
match self {
|
format_graphql(&self.active_registry)
|
||||||
Registries::Single(s) => format_graphql(&s.url),
|
|
||||||
Registries::Multi(m) => format_graphql(&m.current),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the current (active) registry URL
|
/// Sets the current (active) registry URL
|
||||||
@ -176,25 +155,17 @@ impl Registries {
|
|||||||
println!("Error when trying to ping registry {registry:?}: {e}");
|
println!("Error when trying to ping registry {registry:?}: {e}");
|
||||||
println!("WARNING: Registry {registry:?} will be used, but commands may not succeed.");
|
println!("WARNING: Registry {registry:?} will be used, but commands may not succeed.");
|
||||||
}
|
}
|
||||||
match self {
|
self.active_registry = registry;
|
||||||
Registries::Single(s) => s.url = registry,
|
|
||||||
Registries::Multi(m) => m.current = registry,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the login token for the registry
|
/// Returns the login token for the registry
|
||||||
pub fn get_login_token_for_registry(&self, registry: &str) -> Option<String> {
|
pub fn get_login_token_for_registry(&self, registry: &str) -> Option<String> {
|
||||||
match self {
|
let registry_formatted = format_graphql(registry);
|
||||||
Registries::Single(s) if s.url == registry || format_graphql(registry) == s.url => {
|
self.tokens
|
||||||
s.token.clone()
|
.iter()
|
||||||
}
|
.filter(|login| login.registry == registry || login.registry == registry_formatted)
|
||||||
Registries::Multi(m) => m
|
.last()
|
||||||
.tokens
|
.map(|login| login.token.clone())
|
||||||
.get(registry)
|
|
||||||
.or_else(|| m.tokens.get(&format_graphql(registry)))
|
|
||||||
.cloned(),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the login token for the registry URL
|
/// Sets the login token for the registry URL
|
||||||
@ -204,38 +175,20 @@ impl Registries {
|
|||||||
token: &str,
|
token: &str,
|
||||||
update_current_registry: UpdateRegistry,
|
update_current_registry: UpdateRegistry,
|
||||||
) {
|
) {
|
||||||
let new_map = match self {
|
let registry_formatted = format_graphql(registry);
|
||||||
Registries::Single(s) => {
|
self.tokens
|
||||||
if s.url == registry {
|
.retain(|login| !(login.registry == registry || login.registry == registry_formatted));
|
||||||
Registries::Single(Registry {
|
self.tokens.push(RegistryLogin {
|
||||||
url: format_graphql(registry),
|
registry: format_graphql(registry),
|
||||||
token: Some(token.to_string()),
|
token: token.to_string(),
|
||||||
})
|
});
|
||||||
} else {
|
if update_current_registry == UpdateRegistry::Update {
|
||||||
let mut map = BTreeMap::new();
|
self.active_registry = format_graphql(registry);
|
||||||
if let Some(token) = s.token.clone() {
|
}
|
||||||
map.insert(format_graphql(&s.url), token);
|
|
||||||
}
|
|
||||||
map.insert(format_graphql(registry), token.to_string());
|
|
||||||
Registries::Multi(MultiRegistry {
|
|
||||||
current: format_graphql(&s.url),
|
|
||||||
tokens: map,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Registries::Multi(m) => {
|
|
||||||
m.tokens.insert(format_graphql(registry), token.to_string());
|
|
||||||
if update_current_registry == UpdateRegistry::Update {
|
|
||||||
m.current = format_graphql(registry);
|
|
||||||
}
|
|
||||||
Registries::Multi(m.clone())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
*self = new_map;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialWapmConfig {
|
impl WasmerConfig {
|
||||||
/// Save the config to a file
|
/// Save the config to a file
|
||||||
pub fn save<P: AsRef<Path>>(&self, to: P) -> anyhow::Result<()> {
|
pub fn save<P: AsRef<Path>>(&self, to: P) -> anyhow::Result<()> {
|
||||||
use std::{fs::File, io::Write};
|
use std::{fs::File, io::Write};
|
||||||
@ -245,33 +198,16 @@ impl PartialWapmConfig {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_file(#[cfg(test)] test_name: &str) -> Result<Self, String> {
|
pub fn from_file(wasmer_dir: &Path) -> Result<Self, String> {
|
||||||
#[cfg(test)]
|
let path = Self::get_file_location(wasmer_dir);
|
||||||
let path = Self::get_file_location(test_name)?;
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let path = Self::get_file_location()?;
|
|
||||||
|
|
||||||
match std::fs::read_to_string(&path) {
|
match std::fs::read_to_string(&path) {
|
||||||
Ok(config_toml) => {
|
Ok(config_toml) => Ok(toml::from_str(&config_toml).unwrap_or_else(|_| Self::default())),
|
||||||
toml::from_str(&config_toml).map_err(|e| format!("could not parse {path:?}: {e}"))
|
|
||||||
}
|
|
||||||
Err(_e) => Ok(Self::default()),
|
Err(_e) => Ok(Self::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_current_dir() -> std::io::Result<PathBuf> {
|
/// Creates and returns the `WASMER_DIR` directory (or $HOME/.wasmer as a fallback)
|
||||||
std::env::current_dir()
|
pub fn get_wasmer_dir() -> Result<PathBuf, String> {
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn get_folder(test_name: &str) -> Result<PathBuf, String> {
|
|
||||||
let test_dir = std::env::temp_dir().join("test_wasmer").join(test_name);
|
|
||||||
let _ = std::fs::create_dir_all(&test_dir);
|
|
||||||
Ok(test_dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(test))]
|
|
||||||
pub fn get_folder() -> Result<PathBuf, String> {
|
|
||||||
Ok(
|
Ok(
|
||||||
if let Some(folder_str) = std::env::var("WASMER_DIR").ok().filter(|s| !s.is_empty()) {
|
if let Some(folder_str) = std::env::var("WASMER_DIR").ok().filter(|s| !s.is_empty()) {
|
||||||
let folder = PathBuf::from(folder_str);
|
let folder = PathBuf::from(folder_str);
|
||||||
@ -294,14 +230,16 @@ impl PartialWapmConfig {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
pub fn get_current_dir() -> std::io::Result<PathBuf> {
|
||||||
pub fn get_file_location(test_name: &str) -> Result<PathBuf, String> {
|
std::env::current_dir()
|
||||||
Ok(Self::get_folder(test_name)?.join(crate::GLOBAL_CONFIG_FILE_NAME))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(test))]
|
pub fn get_file_location(wasmer_dir: &Path) -> PathBuf {
|
||||||
pub fn get_file_location() -> Result<PathBuf, String> {
|
wasmer_dir.join(crate::GLOBAL_CONFIG_FILE_NAME)
|
||||||
Ok(Self::get_folder()?.join(crate::GLOBAL_CONFIG_FILE_NAME))
|
}
|
||||||
|
|
||||||
|
pub fn get_database_file_path(wasmer_dir: &Path) -> PathBuf {
|
||||||
|
wasmer_dir.join(GLOBAL_CONFIG_DATABASE_FILE_NAME)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +99,60 @@ pub(crate) mod proxy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLQuery)]
|
||||||
|
#[graphql(
|
||||||
|
schema_path = "graphql/schema.graphql",
|
||||||
|
query_path = "graphql/queries/get_package_version.graphql",
|
||||||
|
response_derives = "Debug"
|
||||||
|
)]
|
||||||
|
pub(crate) struct GetPackageVersionQuery;
|
||||||
|
|
||||||
|
#[derive(GraphQLQuery)]
|
||||||
|
#[graphql(
|
||||||
|
schema_path = "graphql/schema.graphql",
|
||||||
|
query_path = "graphql/queries/get_package_by_command.graphql",
|
||||||
|
response_derives = "Debug"
|
||||||
|
)]
|
||||||
|
pub(crate) struct GetPackageByCommandQuery;
|
||||||
|
|
||||||
|
#[derive(GraphQLQuery)]
|
||||||
|
#[graphql(
|
||||||
|
schema_path = "graphql/schema.graphql",
|
||||||
|
query_path = "graphql/queries/test_if_registry_present.graphql",
|
||||||
|
response_derives = "Debug"
|
||||||
|
)]
|
||||||
|
pub(crate) struct TestIfRegistryPresent;
|
||||||
|
|
||||||
|
#[derive(GraphQLQuery)]
|
||||||
|
#[graphql(
|
||||||
|
schema_path = "graphql/schema.graphql",
|
||||||
|
query_path = "graphql/queries/get_bindings.graphql",
|
||||||
|
response_derives = "Debug,Clone,PartialEq,Eq"
|
||||||
|
)]
|
||||||
|
pub(crate) struct GetBindingsQuery;
|
||||||
|
|
||||||
|
#[derive(GraphQLQuery)]
|
||||||
|
#[graphql(
|
||||||
|
schema_path = "graphql/schema.graphql",
|
||||||
|
query_path = "graphql/queries/publish_package_chunked.graphql",
|
||||||
|
response_derives = "Debug"
|
||||||
|
)]
|
||||||
|
pub(crate) struct PublishPackageMutationChunked;
|
||||||
|
|
||||||
|
#[derive(GraphQLQuery)]
|
||||||
|
#[graphql(
|
||||||
|
schema_path = "graphql/schema.graphql",
|
||||||
|
query_path = "graphql/queries/get_signed_url.graphql",
|
||||||
|
response_derives = "Debug, Clone"
|
||||||
|
)]
|
||||||
|
pub(crate) struct GetSignedUrl;
|
||||||
|
|
||||||
|
#[cfg(target_os = "wasi")]
|
||||||
|
pub fn whoami_distro() -> String {
|
||||||
|
whoami::os().to_lowercase()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "wasi"))]
|
||||||
pub fn whoami_distro() -> String {
|
pub fn whoami_distro() -> String {
|
||||||
whoami::distro().to_lowercase()
|
whoami::distro().to_lowercase()
|
||||||
}
|
}
|
||||||
|
47
lib/registry/src/interface.rs
Normal file
47
lib/registry/src/interface.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#![cfg_attr(
|
||||||
|
not(feature = "full"),
|
||||||
|
allow(dead_code, unused_imports, unused_variables)
|
||||||
|
)]
|
||||||
|
use crate::graphql::execute_query;
|
||||||
|
use graphql_client::*;
|
||||||
|
|
||||||
|
#[derive(GraphQLQuery)]
|
||||||
|
#[graphql(
|
||||||
|
schema_path = "graphql/schema.graphql",
|
||||||
|
query_path = "graphql/queries/get_interface_version.graphql",
|
||||||
|
response_derives = "Debug"
|
||||||
|
)]
|
||||||
|
struct GetInterfaceVersionQuery;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InterfaceFromServer {
|
||||||
|
pub name: String,
|
||||||
|
pub version: String,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterfaceFromServer {
|
||||||
|
fn get_response(
|
||||||
|
registry: &str,
|
||||||
|
name: String,
|
||||||
|
version: String,
|
||||||
|
) -> anyhow::Result<get_interface_version_query::ResponseData> {
|
||||||
|
let q = GetInterfaceVersionQuery::build_query(get_interface_version_query::Variables {
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
});
|
||||||
|
execute_query(registry, "", &q)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(registry: &str, name: String, version: String) -> anyhow::Result<Self> {
|
||||||
|
let response = Self::get_response(registry, name, version)?;
|
||||||
|
let response_val = response
|
||||||
|
.interface
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Error downloading Interface from the server"))?;
|
||||||
|
Ok(Self {
|
||||||
|
name: response_val.interface.name,
|
||||||
|
version: response_val.version,
|
||||||
|
content: response_val.content,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,6 @@
|
|||||||
//! curl -sSfL https://registry.wapm.io/graphql/schema.graphql > lib/registry/graphql/schema.graphql
|
//! curl -sSfL https://registry.wapm.io/graphql/schema.graphql > lib/registry/graphql/schema.graphql
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use crate::config::Registries;
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use core::ops::Range;
|
use core::ops::Range;
|
||||||
use reqwest::header::{ACCEPT, RANGE};
|
use reqwest::header::{ACCEPT, RANGE};
|
||||||
@ -20,18 +19,22 @@ use url::Url;
|
|||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod graphql;
|
pub mod graphql;
|
||||||
|
pub mod interface;
|
||||||
pub mod login;
|
pub mod login;
|
||||||
pub mod package;
|
pub mod package;
|
||||||
|
pub mod publish;
|
||||||
pub mod queries;
|
pub mod queries;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
config::{format_graphql, PartialWapmConfig},
|
config::{format_graphql, WasmerConfig},
|
||||||
package::Package,
|
package::Package,
|
||||||
queries::get_bindings_query::ProgrammingLanguage,
|
queries::get_bindings_query::ProgrammingLanguage,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub static GLOBAL_CONFIG_FILE_NAME: &str = "wapm.toml";
|
pub static PACKAGE_TOML_FILE_NAME: &str = "wasmer.toml";
|
||||||
|
pub static PACKAGE_TOML_FALLBACK_NAME: &str = "wapm.toml";
|
||||||
|
pub static GLOBAL_CONFIG_FILE_NAME: &str = "wasmer.toml";
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
|
||||||
pub struct PackageDownloadInfo {
|
pub struct PackageDownloadInfo {
|
||||||
@ -45,30 +48,17 @@ pub struct PackageDownloadInfo {
|
|||||||
pub pirita_url: Option<String>,
|
pub pirita_url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_package_local_dir(
|
pub fn get_package_local_dir(wasmer_dir: &Path, url: &str, version: &str) -> Option<PathBuf> {
|
||||||
#[cfg(test)] test_name: &str,
|
let checkouts_dir = get_checkouts_dir(wasmer_dir);
|
||||||
url: &str,
|
|
||||||
version: &str,
|
|
||||||
) -> Option<PathBuf> {
|
|
||||||
#[cfg(test)]
|
|
||||||
let checkouts_dir = get_checkouts_dir(test_name)?;
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let checkouts_dir = get_checkouts_dir()?;
|
|
||||||
let url_hash = Package::hash_url(url);
|
let url_hash = Package::hash_url(url);
|
||||||
let dir = checkouts_dir.join(format!("{url_hash}@{version}"));
|
let dir = checkouts_dir.join(format!("{url_hash}@{version}"));
|
||||||
Some(dir)
|
Some(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_finding_local_command(#[cfg(test)] test_name: &str, cmd: &str) -> Option<LocalPackage> {
|
pub fn try_finding_local_command(wasmer_dir: &Path, cmd: &str) -> Option<LocalPackage> {
|
||||||
#[cfg(test)]
|
let local_packages = get_all_local_packages(wasmer_dir);
|
||||||
let local_packages = get_all_local_packages(test_name);
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let local_packages = get_all_local_packages();
|
|
||||||
for p in local_packages {
|
for p in local_packages {
|
||||||
#[cfg(not(test))]
|
|
||||||
let commands = p.get_commands();
|
let commands = p.get_commands();
|
||||||
#[cfg(test)]
|
|
||||||
let commands = p.get_commands(test_name);
|
|
||||||
|
|
||||||
if commands.unwrap_or_default().iter().any(|c| c == cmd) {
|
if commands.unwrap_or_default().iter().any(|c| c == cmd) {
|
||||||
return Some(p);
|
return Some(p);
|
||||||
@ -85,20 +75,59 @@ pub struct LocalPackage {
|
|||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for LocalPackage {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}@{} (registry {}, located at {})",
|
||||||
|
self.name,
|
||||||
|
self.version,
|
||||||
|
self.registry,
|
||||||
|
self.path.display()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl LocalPackage {
|
impl LocalPackage {
|
||||||
pub fn get_path(&self, #[cfg(test)] test_name: &str) -> Result<PathBuf, String> {
|
pub fn get_path(&self) -> Result<PathBuf, String> {
|
||||||
Ok(self.path.clone())
|
Ok(self.path.clone())
|
||||||
}
|
}
|
||||||
pub fn get_commands(&self, #[cfg(test)] test_name: &str) -> Result<Vec<String>, String> {
|
|
||||||
#[cfg(not(test))]
|
/// Returns the wasmer.toml path if it exists
|
||||||
|
pub fn get_wasmer_toml_path(base_path: &Path) -> Result<PathBuf, anyhow::Error> {
|
||||||
|
let path = base_path.join(PACKAGE_TOML_FILE_NAME);
|
||||||
|
let fallback_path = base_path.join(PACKAGE_TOML_FALLBACK_NAME);
|
||||||
|
if path.exists() {
|
||||||
|
Ok(path)
|
||||||
|
} else if fallback_path.exists() {
|
||||||
|
Ok(fallback_path)
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"neither {} nor {} exists",
|
||||||
|
path.display(),
|
||||||
|
fallback_path.display()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the wasmer.toml fron $PATH with wapm.toml as a fallback
|
||||||
|
pub fn read_toml(base_path: &Path) -> Result<wasmer_toml::Manifest, String> {
|
||||||
|
let wasmer_toml = std::fs::read_to_string(base_path.join(PACKAGE_TOML_FILE_NAME))
|
||||||
|
.or_else(|_| std::fs::read_to_string(base_path.join(PACKAGE_TOML_FALLBACK_NAME)))
|
||||||
|
.map_err(|_| {
|
||||||
|
format!(
|
||||||
|
"Path {} has no {PACKAGE_TOML_FILE_NAME} or {PACKAGE_TOML_FALLBACK_NAME}",
|
||||||
|
base_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let wasmer_toml = toml::from_str::<wasmer_toml::Manifest>(&wasmer_toml)
|
||||||
|
.map_err(|e| format!("Could not parse toml for {:?}: {e}", base_path.display()))?;
|
||||||
|
Ok(wasmer_toml)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_commands(&self) -> Result<Vec<String>, String> {
|
||||||
let path = self.get_path()?;
|
let path = self.get_path()?;
|
||||||
#[cfg(test)]
|
let toml_parsed = Self::read_toml(&path)?;
|
||||||
let path = self.get_path(test_name)?;
|
|
||||||
let toml_path = path.join("wapm.toml");
|
|
||||||
let toml = std::fs::read_to_string(&toml_path)
|
|
||||||
.map_err(|e| format!("error reading {}: {e}", toml_path.display()))?;
|
|
||||||
let toml_parsed = toml::from_str::<wapm_toml::Manifest>(&toml)
|
|
||||||
.map_err(|e| format!("error parsing {}: {e}", toml_path.display()))?;
|
|
||||||
Ok(toml_parsed
|
Ok(toml_parsed
|
||||||
.command
|
.command
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
@ -110,19 +139,15 @@ impl LocalPackage {
|
|||||||
|
|
||||||
/// Returns the (manifest, .wasm file name), given a package dir
|
/// Returns the (manifest, .wasm file name), given a package dir
|
||||||
pub fn get_executable_file_from_path(
|
pub fn get_executable_file_from_path(
|
||||||
package_dir: &PathBuf,
|
package_dir: &Path,
|
||||||
command: Option<&str>,
|
command: Option<&str>,
|
||||||
) -> Result<(wapm_toml::Manifest, PathBuf), anyhow::Error> {
|
) -> Result<(wasmer_toml::Manifest, PathBuf), anyhow::Error> {
|
||||||
let wapm_toml = std::fs::read_to_string(package_dir.join("wapm.toml"))
|
let wasmer_toml = LocalPackage::read_toml(package_dir).map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||||
.map_err(|_| anyhow::anyhow!("Package {package_dir:?} has no wapm.toml"))?;
|
|
||||||
|
|
||||||
let wapm_toml = toml::from_str::<wapm_toml::Manifest>(&wapm_toml)
|
let name = wasmer_toml.package.name.clone();
|
||||||
.map_err(|e| anyhow::anyhow!("Could not parse toml for {package_dir:?}: {e}"))?;
|
let version = wasmer_toml.package.version.clone();
|
||||||
|
|
||||||
let name = wapm_toml.package.name.clone();
|
let commands = wasmer_toml.command.clone().unwrap_or_default();
|
||||||
let version = wapm_toml.package.version.clone();
|
|
||||||
|
|
||||||
let commands = wapm_toml.command.clone().unwrap_or_default();
|
|
||||||
let entrypoint_module = match command {
|
let entrypoint_module = match command {
|
||||||
Some(s) => commands.iter().find(|c| c.get_name() == s).ok_or_else(|| {
|
Some(s) => commands.iter().find(|c| c.get_name() == s).ok_or_else(|| {
|
||||||
anyhow::anyhow!("Cannot run {name}@{version}: package has no command {s:?}")
|
anyhow::anyhow!("Cannot run {name}@{version}: package has no command {s:?}")
|
||||||
@ -144,19 +169,19 @@ pub fn get_executable_file_from_path(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let module_name = entrypoint_module.get_module();
|
let module_name = entrypoint_module.get_module();
|
||||||
let modules = wapm_toml.module.clone().unwrap_or_default();
|
let modules = wasmer_toml.module.clone().unwrap_or_default();
|
||||||
let entrypoint_module = modules
|
let entrypoint_module = modules
|
||||||
.iter()
|
.iter()
|
||||||
.find(|m| m.name == module_name)
|
.find(|m| m.name == module_name)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
anyhow::anyhow!(
|
anyhow::anyhow!(
|
||||||
"Cannot run {name}@{version}: module {module_name} not found in wapm.toml"
|
"Cannot run {name}@{version}: module {module_name} not found in {GLOBAL_CONFIG_FILE_NAME}"
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let entrypoint_source = package_dir.join(&entrypoint_module.source);
|
let entrypoint_source = package_dir.join(&entrypoint_module.source);
|
||||||
|
|
||||||
Ok((wapm_toml, entrypoint_source))
|
Ok((wasmer_toml, entrypoint_source))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_all_names_in_dir(dir: &PathBuf) -> Vec<(PathBuf, String)> {
|
fn get_all_names_in_dir(dir: &PathBuf) -> Vec<(PathBuf, String)> {
|
||||||
@ -185,25 +210,13 @@ fn get_all_names_in_dir(dir: &PathBuf) -> Vec<(PathBuf, String)> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a list of all locally installed packages
|
/// Returns a list of all locally installed packages
|
||||||
pub fn get_all_local_packages(#[cfg(test)] test_name: &str) -> Vec<LocalPackage> {
|
pub fn get_all_local_packages(wasmer_dir: &Path) -> Vec<LocalPackage> {
|
||||||
let mut packages = Vec::new();
|
let mut packages = Vec::new();
|
||||||
|
|
||||||
#[cfg(not(test))]
|
let checkouts_dir = get_checkouts_dir(wasmer_dir);
|
||||||
let checkouts_dir = get_checkouts_dir();
|
|
||||||
#[cfg(test)]
|
|
||||||
let checkouts_dir = get_checkouts_dir(test_name);
|
|
||||||
|
|
||||||
let checkouts_dir = match checkouts_dir {
|
|
||||||
Some(s) => s,
|
|
||||||
None => return packages,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (path, url_hash_with_version) in get_all_names_in_dir(&checkouts_dir) {
|
for (path, url_hash_with_version) in get_all_names_in_dir(&checkouts_dir) {
|
||||||
let s = match std::fs::read_to_string(path.join("wapm.toml")) {
|
let manifest = match LocalPackage::read_toml(&path) {
|
||||||
Ok(o) => o,
|
|
||||||
Err(_) => continue,
|
|
||||||
};
|
|
||||||
let manifest = match wapm_toml::Manifest::parse(&s) {
|
|
||||||
Ok(o) => o,
|
Ok(o) => o,
|
||||||
Err(_) => continue,
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
@ -229,16 +242,11 @@ pub fn get_all_local_packages(#[cfg(test)] test_name: &str) -> Vec<LocalPackage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_local_package(
|
pub fn get_local_package(
|
||||||
#[cfg(test)] test_name: &str,
|
wasmer_dir: &Path,
|
||||||
name: &str,
|
name: &str,
|
||||||
version: Option<&str>,
|
version: Option<&str>,
|
||||||
) -> Option<LocalPackage> {
|
) -> Option<LocalPackage> {
|
||||||
#[cfg(not(test))]
|
get_all_local_packages(wasmer_dir)
|
||||||
let local_packages = get_all_local_packages();
|
|
||||||
#[cfg(test)]
|
|
||||||
let local_packages = get_all_local_packages(test_name);
|
|
||||||
|
|
||||||
local_packages
|
|
||||||
.iter()
|
.iter()
|
||||||
.find(|p| {
|
.find(|p| {
|
||||||
if p.name != name {
|
if p.name != name {
|
||||||
@ -367,7 +375,7 @@ pub fn query_package_from_registry(
|
|||||||
QueryPackageError::ErrorSendingQuery(format!("no package version for {name:?}"))
|
QueryPackageError::ErrorSendingQuery(format!("no package version for {name:?}"))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let manifest = toml::from_str::<wapm_toml::Manifest>(&v.manifest).map_err(|e| {
|
let manifest = toml::from_str::<wasmer_toml::Manifest>(&v.manifest).map_err(|e| {
|
||||||
QueryPackageError::ErrorSendingQuery(format!("Invalid manifest for crate {name:?}: {e}"))
|
QueryPackageError::ErrorSendingQuery(format!("Invalid manifest for crate {name:?}: {e}"))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@ -392,43 +400,12 @@ pub fn query_package_from_registry(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_wasmer_root_dir(#[cfg(test)] test_name: &str) -> Option<PathBuf> {
|
pub fn get_checkouts_dir(wasmer_dir: &Path) -> PathBuf {
|
||||||
#[cfg(test)]
|
wasmer_dir.join("checkouts")
|
||||||
{
|
|
||||||
PartialWapmConfig::get_folder(test_name).ok()
|
|
||||||
}
|
|
||||||
#[cfg(not(test))]
|
|
||||||
{
|
|
||||||
PartialWapmConfig::get_folder().ok()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_checkouts_dir(#[cfg(test)] test_name: &str) -> Option<PathBuf> {
|
pub fn get_webc_dir(wasmer_dir: &Path) -> PathBuf {
|
||||||
#[cfg(test)]
|
wasmer_dir.join("webc")
|
||||||
let root_dir = get_wasmer_root_dir(test_name)?;
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let root_dir = get_wasmer_root_dir()?;
|
|
||||||
Some(root_dir.join("checkouts"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_webc_dir(#[cfg(test)] test_name: &str) -> Option<PathBuf> {
|
|
||||||
#[cfg(test)]
|
|
||||||
let root_dir = get_wasmer_root_dir(test_name)?;
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let root_dir = get_wasmer_root_dir()?;
|
|
||||||
Some(root_dir.join("webc"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returs the path to the directory where all packages on this computer are being stored
|
|
||||||
pub fn get_global_install_dir(
|
|
||||||
#[cfg(test)] test_name: &str,
|
|
||||||
registry_host: &str,
|
|
||||||
) -> Option<PathBuf> {
|
|
||||||
#[cfg(test)]
|
|
||||||
let root_dir = get_checkouts_dir(test_name)?;
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let root_dir = get_checkouts_dir()?;
|
|
||||||
Some(root_dir.join(registry_host))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function that will unpack .tar.gz files and .tar.bz
|
/// Convenience function that will unpack .tar.gz files and .tar.bz
|
||||||
@ -538,7 +515,7 @@ where
|
|||||||
|
|
||||||
/// Installs the .tar.gz if it doesn't yet exist, returns the
|
/// Installs the .tar.gz if it doesn't yet exist, returns the
|
||||||
/// (package dir, entrypoint .wasm file path)
|
/// (package dir, entrypoint .wasm file path)
|
||||||
pub fn install_package(#[cfg(test)] test_name: &str, url: &Url) -> Result<PathBuf, anyhow::Error> {
|
pub fn install_package(wasmer_dir: &Path, url: &Url) -> Result<PathBuf, anyhow::Error> {
|
||||||
use fs_extra::dir::copy;
|
use fs_extra::dir::copy;
|
||||||
|
|
||||||
let tempdir = tempdir::TempDir::new("download")
|
let tempdir = tempdir::TempDir::new("download")
|
||||||
@ -563,21 +540,13 @@ pub fn install_package(#[cfg(test)] test_name: &str, url: &Url) -> Result<PathBu
|
|||||||
)
|
)
|
||||||
.with_context(|| anyhow::anyhow!("Could not unpack file downloaded from {url}"))?;
|
.with_context(|| anyhow::anyhow!("Could not unpack file downloaded from {url}"))?;
|
||||||
|
|
||||||
// read {unpacked}/wapm.toml to get the name + version number
|
// read {unpacked}/wasmer.toml to get the name + version number
|
||||||
let toml_path = unpacked_targz_path.join("wapm.toml");
|
let toml_parsed = LocalPackage::read_toml(&unpacked_targz_path)
|
||||||
let toml = std::fs::read_to_string(&toml_path)
|
.map_err(|e| anyhow::anyhow!("error reading package name / version number: {e}"))?;
|
||||||
.map_err(|e| anyhow::anyhow!("error reading {}: {e}", toml_path.display()))?;
|
|
||||||
let toml_parsed = toml::from_str::<wapm_toml::Manifest>(&toml)
|
|
||||||
.map_err(|e| anyhow::anyhow!("error parsing {}: {e}", toml_path.display()))?;
|
|
||||||
|
|
||||||
let version = toml_parsed.package.version.to_string();
|
let version = toml_parsed.package.version.to_string();
|
||||||
|
|
||||||
#[cfg(test)]
|
let checkouts_dir = crate::get_checkouts_dir(wasmer_dir);
|
||||||
let checkouts_dir = crate::get_checkouts_dir(test_name);
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let checkouts_dir = crate::get_checkouts_dir();
|
|
||||||
|
|
||||||
let checkouts_dir = checkouts_dir.ok_or_else(|| anyhow::anyhow!("no checkouts dir"))?;
|
|
||||||
|
|
||||||
let installation_path =
|
let installation_path =
|
||||||
checkouts_dir.join(format!("{}@{version}", Package::hash_url(url.as_ref())));
|
checkouts_dir.join(format!("{}@{version}", Package::hash_url(url.as_ref())));
|
||||||
@ -592,7 +561,7 @@ pub fn install_package(#[cfg(test)] test_name: &str, url: &Url) -> Result<PathBu
|
|||||||
|
|
||||||
#[cfg(not(target_os = "wasi"))]
|
#[cfg(not(target_os = "wasi"))]
|
||||||
let _ = filetime::set_file_mtime(
|
let _ = filetime::set_file_mtime(
|
||||||
installation_path.join("wapm.toml"),
|
LocalPackage::get_wasmer_toml_path(&installation_path)?,
|
||||||
filetime::FileTime::now(),
|
filetime::FileTime::now(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -600,16 +569,14 @@ pub fn install_package(#[cfg(test)] test_name: &str, url: &Url) -> Result<PathBu
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn whoami(
|
pub fn whoami(
|
||||||
#[cfg(test)] test_name: &str,
|
wasmer_dir: &Path,
|
||||||
registry: Option<&str>,
|
registry: Option<&str>,
|
||||||
|
token: Option<&str>,
|
||||||
) -> Result<(String, String), anyhow::Error> {
|
) -> Result<(String, String), anyhow::Error> {
|
||||||
use crate::queries::{who_am_i_query, WhoAmIQuery};
|
use crate::queries::{who_am_i_query, WhoAmIQuery};
|
||||||
use graphql_client::GraphQLQuery;
|
use graphql_client::GraphQLQuery;
|
||||||
|
|
||||||
#[cfg(test)]
|
let config = WasmerConfig::from_file(wasmer_dir);
|
||||||
let config = PartialWapmConfig::from_file(test_name);
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let config = PartialWapmConfig::from_file();
|
|
||||||
|
|
||||||
let config = config
|
let config = config
|
||||||
.map_err(|e| anyhow::anyhow!("{e}"))
|
.map_err(|e| anyhow::anyhow!("{e}"))
|
||||||
@ -620,9 +587,9 @@ pub fn whoami(
|
|||||||
None => config.registry.get_current_registry(),
|
None => config.registry.get_current_registry(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let login_token = config
|
let login_token = token
|
||||||
.registry
|
.map(|s| s.to_string())
|
||||||
.get_login_token_for_registry(®istry)
|
.or_else(|| config.registry.get_login_token_for_registry(®istry))
|
||||||
.ok_or_else(|| anyhow::anyhow!("not logged into registry {:?}", registry))?;
|
.ok_or_else(|| anyhow::anyhow!("not logged into registry {:?}", registry))?;
|
||||||
|
|
||||||
let q = WhoAmIQuery::build_query(who_am_i_query::Variables {});
|
let q = WhoAmIQuery::build_query(who_am_i_query::Variables {});
|
||||||
@ -657,22 +624,11 @@ pub fn test_if_registry_present(registry: &str) -> Result<bool, String> {
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_all_available_registries(#[cfg(test)] test_name: &str) -> Result<Vec<String>, String> {
|
pub fn get_all_available_registries(wasmer_dir: &Path) -> Result<Vec<String>, String> {
|
||||||
#[cfg(test)]
|
let config = WasmerConfig::from_file(wasmer_dir)?;
|
||||||
let config = PartialWapmConfig::from_file(test_name)?;
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let config = PartialWapmConfig::from_file()?;
|
|
||||||
|
|
||||||
let mut registries = Vec::new();
|
let mut registries = Vec::new();
|
||||||
match config.registry {
|
for login in config.registry.tokens {
|
||||||
Registries::Single(s) => {
|
registries.push(format_graphql(&login.registry));
|
||||||
registries.push(format_graphql(&s.url));
|
|
||||||
}
|
|
||||||
Registries::Multi(m) => {
|
|
||||||
for key in m.tokens.keys() {
|
|
||||||
registries.push(format_graphql(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(registries)
|
Ok(registries)
|
||||||
}
|
}
|
||||||
@ -684,7 +640,7 @@ pub struct RemoteWebcInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn install_webc_package(
|
pub fn install_webc_package(
|
||||||
#[cfg(test)] test_name: &str,
|
wasmer_dir: &Path,
|
||||||
url: &Url,
|
url: &Url,
|
||||||
checksum: &str,
|
checksum: &str,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
@ -692,34 +648,18 @@ pub fn install_webc_package(
|
|||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.block_on(async {
|
.block_on(async { install_webc_package_inner(wasmer_dir, url, checksum).await })
|
||||||
{
|
|
||||||
#[cfg(test)]
|
|
||||||
{
|
|
||||||
install_webc_package_inner(test_name, url, checksum).await
|
|
||||||
}
|
|
||||||
#[cfg(not(test))]
|
|
||||||
{
|
|
||||||
install_webc_package_inner(url, checksum).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn install_webc_package_inner(
|
async fn install_webc_package_inner(
|
||||||
#[cfg(test)] test_name: &str,
|
wasmer_dir: &Path,
|
||||||
url: &Url,
|
url: &Url,
|
||||||
checksum: &str,
|
checksum: &str,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
|
|
||||||
#[cfg(test)]
|
let path = get_webc_dir(wasmer_dir);
|
||||||
let path = get_webc_dir(test_name).ok_or_else(|| anyhow::anyhow!("no webc dir"))?;
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let path = get_webc_dir().ok_or_else(|| anyhow::anyhow!("no webc dir"))?;
|
|
||||||
|
|
||||||
let _ = std::fs::create_dir_all(&path);
|
let _ = std::fs::create_dir_all(&path);
|
||||||
|
|
||||||
let webc_path = path.join(checksum);
|
let webc_path = path.join(checksum);
|
||||||
|
|
||||||
let mut file = std::fs::File::create(&webc_path)
|
let mut file = std::fs::File::create(&webc_path)
|
||||||
@ -763,28 +703,12 @@ async fn install_webc_package_inner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a list of all installed webc packages
|
/// Returns a list of all installed webc packages
|
||||||
#[cfg(test)]
|
pub fn get_all_installed_webc_packages(wasmer_dir: &Path) -> Vec<RemoteWebcInfo> {
|
||||||
pub fn get_all_installed_webc_packages(test_name: &str) -> Vec<RemoteWebcInfo> {
|
get_all_installed_webc_packages_inner(wasmer_dir)
|
||||||
get_all_installed_webc_packages_inner(test_name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(test))]
|
fn get_all_installed_webc_packages_inner(wasmer_dir: &Path) -> Vec<RemoteWebcInfo> {
|
||||||
pub fn get_all_installed_webc_packages() -> Vec<RemoteWebcInfo> {
|
let dir = get_webc_dir(wasmer_dir);
|
||||||
get_all_installed_webc_packages_inner("")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_all_installed_webc_packages_inner(_test_name: &str) -> Vec<RemoteWebcInfo> {
|
|
||||||
#[cfg(test)]
|
|
||||||
let dir = match get_webc_dir(_test_name) {
|
|
||||||
Some(s) => s,
|
|
||||||
None => return Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let dir = match get_webc_dir() {
|
|
||||||
Some(s) => s,
|
|
||||||
None => return Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let read_dir = match std::fs::read_dir(dir) {
|
let read_dir = match std::fs::read_dir(dir) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
@ -984,8 +908,6 @@ fn get_bytes(
|
|||||||
#[cfg(not(target_env = "musl"))]
|
#[cfg(not(target_env = "musl"))]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_install_package() {
|
fn test_install_package() {
|
||||||
const TEST_NAME: &str = "test_install_package";
|
|
||||||
|
|
||||||
println!("test install package...");
|
println!("test install package...");
|
||||||
let registry = "https://registry.wapm.io/graphql";
|
let registry = "https://registry.wapm.io/graphql";
|
||||||
if !test_if_registry_present(registry).unwrap_or(false) {
|
if !test_if_registry_present(registry).unwrap_or(false) {
|
||||||
@ -1009,25 +931,25 @@ fn test_install_package() {
|
|||||||
"https://registry-cdn.wapm.io/packages/wasmer/wabt/wabt-1.0.29.tar.gz".to_string()
|
"https://registry-cdn.wapm.io/packages/wasmer/wabt/wabt-1.0.29.tar.gz".to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
let path = install_package(TEST_NAME, &url::Url::parse(&wabt.url).unwrap()).unwrap();
|
let fake_wasmer_dir = tempdir::TempDir::new("tmp").unwrap();
|
||||||
|
let wasmer_dir = fake_wasmer_dir.path();
|
||||||
|
let path = install_package(wasmer_dir, &url::Url::parse(&wabt.url).unwrap()).unwrap();
|
||||||
|
|
||||||
println!("package installed: {path:?}");
|
println!("package installed: {path:?}");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
path,
|
path,
|
||||||
get_checkouts_dir(TEST_NAME)
|
get_checkouts_dir(wasmer_dir).join(&format!("{}@1.0.29", Package::hash_url(&wabt.url)))
|
||||||
.unwrap()
|
|
||||||
.join(&format!("{}@1.0.29", Package::hash_url(&wabt.url)))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let all_installed_packages = get_all_local_packages(TEST_NAME);
|
let all_installed_packages = get_all_local_packages(wasmer_dir);
|
||||||
|
|
||||||
let is_installed = all_installed_packages
|
let is_installed = all_installed_packages
|
||||||
.iter()
|
.iter()
|
||||||
.any(|p| p.name == "wasmer/wabt" && p.version == "1.0.29");
|
.any(|p| p.name == "wasmer/wabt" && p.version == "1.0.29");
|
||||||
|
|
||||||
if !is_installed {
|
if !is_installed {
|
||||||
let panic_str = get_all_local_packages(TEST_NAME)
|
let panic_str = get_all_local_packages(wasmer_dir)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| format!("{} {} {}", p.registry, p.name, p.version))
|
.map(|p| format!("{} {} {}", p.registry, p.name, p.version))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
|
@ -1,33 +1,25 @@
|
|||||||
use crate::config::{format_graphql, UpdateRegistry};
|
use crate::config::{format_graphql, UpdateRegistry};
|
||||||
use crate::PartialWapmConfig;
|
use crate::WasmerConfig;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
/// Login to a registry and save the token associated with it.
|
/// Login to a registry and save the token associated with it.
|
||||||
///
|
///
|
||||||
/// Also sets the registry as the currently active registry to provide a better UX.
|
/// Also sets the registry as the currently active registry to provide a better UX.
|
||||||
pub fn login_and_save_token(
|
pub fn login_and_save_token(
|
||||||
#[cfg(test)] test_name: &str,
|
wasmer_dir: &Path,
|
||||||
registry: &str,
|
registry: &str,
|
||||||
token: &str,
|
token: &str,
|
||||||
) -> Result<Option<String>, anyhow::Error> {
|
) -> Result<Option<String>, anyhow::Error> {
|
||||||
let registry = format_graphql(registry);
|
let registry = format_graphql(registry);
|
||||||
#[cfg(test)]
|
let mut config = WasmerConfig::from_file(wasmer_dir)
|
||||||
let mut config = PartialWapmConfig::from_file(test_name)
|
|
||||||
.map_err(|e| anyhow::anyhow!("config from file: {e}"))?;
|
.map_err(|e| anyhow::anyhow!("config from file: {e}"))?;
|
||||||
#[cfg(not(test))]
|
|
||||||
let mut config =
|
|
||||||
PartialWapmConfig::from_file().map_err(|e| anyhow::anyhow!("config from file: {e}"))?;
|
|
||||||
config.registry.set_current_registry(®istry);
|
config.registry.set_current_registry(®istry);
|
||||||
config.registry.set_login_token_for_registry(
|
config.registry.set_login_token_for_registry(
|
||||||
&config.registry.get_current_registry(),
|
&config.registry.get_current_registry(),
|
||||||
token,
|
token,
|
||||||
UpdateRegistry::Update,
|
UpdateRegistry::Update,
|
||||||
);
|
);
|
||||||
#[cfg(test)]
|
let path = WasmerConfig::get_file_location(wasmer_dir);
|
||||||
let path = PartialWapmConfig::get_file_location(test_name)
|
|
||||||
.map_err(|e| anyhow::anyhow!("get file location: {e}"))?;
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let path = PartialWapmConfig::get_file_location()
|
|
||||||
.map_err(|e| anyhow::anyhow!("get file location: {e}"))?;
|
|
||||||
config.save(&path)?;
|
config.save(&path)?;
|
||||||
crate::utils::get_username_registry_token(®istry, token)
|
crate::utils::get_username_registry_token(®istry, token)
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
use crate::PartialWapmConfig;
|
use crate::WasmerConfig;
|
||||||
use std::path::PathBuf;
|
use regex::Regex;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
use std::{fmt, str::FromStr};
|
use std::{fmt, str::FromStr};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
const REGEX_PACKAGE_WITH_VERSION: &str =
|
||||||
|
r#"^([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_]+)(@([a-zA-Z0-9\.\-_]+*))?$"#;
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref PACKAGE_WITH_VERSION: Regex = regex::Regex::new(REGEX_PACKAGE_WITH_VERSION).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Package {
|
pub struct Package {
|
||||||
pub namespace: String,
|
pub namespace: String,
|
||||||
@ -18,16 +26,11 @@ impl fmt::Display for Package {
|
|||||||
|
|
||||||
impl Package {
|
impl Package {
|
||||||
/// Checks whether the package is already installed, if yes, returns the path to the root dir
|
/// Checks whether the package is already installed, if yes, returns the path to the root dir
|
||||||
pub fn already_installed(&self, #[cfg(test)] test_name: &str) -> Option<PathBuf> {
|
pub fn already_installed(&self, wasmer_dir: &Path) -> Option<PathBuf> {
|
||||||
#[cfg(not(test))]
|
let checkouts_dir = crate::get_checkouts_dir(wasmer_dir);
|
||||||
let checkouts_dir = crate::get_checkouts_dir()?;
|
let config = WasmerConfig::from_file(wasmer_dir).ok()?;
|
||||||
#[cfg(test)]
|
let current_registry = config.registry.get_current_registry();
|
||||||
let checkouts_dir = crate::get_checkouts_dir(test_name)?;
|
let hash = self.get_hash(¤t_registry);
|
||||||
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let hash = self.get_hash();
|
|
||||||
#[cfg(test)]
|
|
||||||
let hash = self.get_hash(test_name);
|
|
||||||
|
|
||||||
let found = std::fs::read_dir(&checkouts_dir)
|
let found = std::fs::read_dir(&checkouts_dir)
|
||||||
.ok()?
|
.ok()?
|
||||||
@ -41,11 +44,8 @@ impl Package {
|
|||||||
|
|
||||||
/// Checks if the URL is already installed, note that `{url}@{version}`
|
/// Checks if the URL is already installed, note that `{url}@{version}`
|
||||||
/// and `{url}` are treated the same
|
/// and `{url}` are treated the same
|
||||||
pub fn is_url_already_installed(url: &Url, #[cfg(test)] test_name: &str) -> Option<PathBuf> {
|
pub fn is_url_already_installed(url: &Url, wasmer_dir: &Path) -> Option<PathBuf> {
|
||||||
#[cfg(not(test))]
|
let checkouts_dir = crate::get_checkouts_dir(wasmer_dir);
|
||||||
let checkouts_dir = crate::get_checkouts_dir()?;
|
|
||||||
#[cfg(test)]
|
|
||||||
let checkouts_dir = crate::get_checkouts_dir(test_name)?;
|
|
||||||
|
|
||||||
let url_string = url.to_string();
|
let url_string = url.to_string();
|
||||||
let (url, version) = match url_string.split('@').collect::<Vec<_>>()[..] {
|
let (url, version) = match url_string.split('@').collect::<Vec<_>>()[..] {
|
||||||
@ -76,22 +76,13 @@ impl Package {
|
|||||||
|
|
||||||
/// Returns the hash of the package URL without the version
|
/// Returns the hash of the package URL without the version
|
||||||
/// (because the version is encoded as @version and isn't part of the hash itself)
|
/// (because the version is encoded as @version and isn't part of the hash itself)
|
||||||
pub fn get_hash(&self, #[cfg(test)] test_name: &str) -> String {
|
pub fn get_hash(&self, registry: &str) -> String {
|
||||||
#[cfg(test)]
|
let url = self.get_url_without_version(registry);
|
||||||
let url = self.get_url_without_version(test_name);
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let url = self.get_url_without_version();
|
|
||||||
Self::hash_url(&url.unwrap_or_default())
|
Self::hash_url(&url.unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_url_without_version(
|
fn get_url_without_version(&self, registry: &str) -> Result<String, anyhow::Error> {
|
||||||
&self,
|
let url = self.url(registry);
|
||||||
#[cfg(test)] test_name: &str,
|
|
||||||
) -> Result<String, anyhow::Error> {
|
|
||||||
#[cfg(test)]
|
|
||||||
let url = self.url(test_name);
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let url = self.url();
|
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"{}/{}/{}",
|
"{}/{}/{}",
|
||||||
url?.origin().ascii_serialization(),
|
url?.origin().ascii_serialization(),
|
||||||
@ -116,16 +107,9 @@ impl Package {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the full URL including the version for this package
|
/// Returns the full URL including the version for this package
|
||||||
pub fn url(&self, #[cfg(test)] test_name: &str) -> Result<Url, anyhow::Error> {
|
pub fn url(&self, registry: &str) -> Result<Url, anyhow::Error> {
|
||||||
#[cfg(test)]
|
|
||||||
let config = PartialWapmConfig::from_file(test_name)
|
|
||||||
.map_err(|e| anyhow::anyhow!("could not read wapm config: {e}"))?;
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let config = PartialWapmConfig::from_file()
|
|
||||||
.map_err(|e| anyhow::anyhow!("could not read wapm config: {e}"))?;
|
|
||||||
let registry = config.registry.get_current_registry();
|
|
||||||
let registry_tld = tldextract::TldExtractor::new(tldextract::TldOption::default())
|
let registry_tld = tldextract::TldExtractor::new(tldextract::TldOption::default())
|
||||||
.extract(®istry)
|
.extract(registry)
|
||||||
.map_err(|e| anyhow::anyhow!("Invalid registry: {}: {e}", registry))?;
|
.map_err(|e| anyhow::anyhow!("Invalid registry: {}: {e}", registry))?;
|
||||||
|
|
||||||
let registry_tld = format!(
|
let registry_tld = format!(
|
||||||
@ -148,18 +132,11 @@ impl Package {
|
|||||||
|
|
||||||
/// Returns the path to the installation directory.
|
/// Returns the path to the installation directory.
|
||||||
/// Does not check whether the installation directory already exists.
|
/// Does not check whether the installation directory already exists.
|
||||||
pub fn get_path(&self, #[cfg(test)] test_name: &str) -> Result<PathBuf, anyhow::Error> {
|
pub fn get_path(&self, wasmer_dir: &Path) -> Result<PathBuf, anyhow::Error> {
|
||||||
#[cfg(test)]
|
let checkouts_dir = crate::get_checkouts_dir(wasmer_dir);
|
||||||
let checkouts_dir = crate::get_checkouts_dir(test_name);
|
let config = WasmerConfig::from_file(wasmer_dir)
|
||||||
#[cfg(not(test))]
|
.map_err(|e| anyhow::anyhow!("could not load config {e}"))?;
|
||||||
let checkouts_dir = crate::get_checkouts_dir();
|
let hash = self.get_hash(&config.registry.get_current_registry());
|
||||||
|
|
||||||
let checkouts_dir = checkouts_dir.ok_or_else(|| anyhow::anyhow!("no checkouts dir"))?;
|
|
||||||
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let hash = self.get_hash();
|
|
||||||
#[cfg(test)]
|
|
||||||
let hash = self.get_hash(test_name);
|
|
||||||
|
|
||||||
match self.version.as_ref() {
|
match self.version.as_ref() {
|
||||||
Some(v) => Ok(checkouts_dir.join(format!("{}@{}", hash, v))),
|
Some(v) => Ok(checkouts_dir.join(format!("{}@{}", hash, v))),
|
||||||
@ -172,11 +149,7 @@ impl FromStr for Package {
|
|||||||
type Err = anyhow::Error;
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let regex =
|
let captures = PACKAGE_WITH_VERSION
|
||||||
regex::Regex::new(r#"^([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_]+)(@([a-zA-Z0-9\.\-_]+*))?$"#)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let captures = regex
|
|
||||||
.captures(s.trim())
|
.captures(s.trim())
|
||||||
.map(|c| {
|
.map(|c| {
|
||||||
c.iter()
|
c.iter()
|
||||||
|
241
lib/registry/src/publish.rs
Normal file
241
lib/registry/src/publish.rs
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
use crate::graphql::{execute_query_modifier_inner, get_signed_url, GetSignedUrl};
|
||||||
|
use crate::graphql::{publish_package_mutation_chunked, PublishPackageMutationChunked};
|
||||||
|
use crate::{format_graphql, WasmerConfig};
|
||||||
|
use console::{style, Emoji};
|
||||||
|
use graphql_client::GraphQLQuery;
|
||||||
|
use indicatif::{ProgressBar, ProgressState, ProgressStyle};
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::fmt::Write;
|
||||||
|
use std::io::BufRead;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
static UPLOAD: Emoji<'_, '_> = Emoji("⬆️ ", "");
|
||||||
|
static PACKAGE: Emoji<'_, '_> = Emoji("📦 ", "");
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum SignArchiveResult {
|
||||||
|
Ok {
|
||||||
|
public_key_id: String,
|
||||||
|
signature: String,
|
||||||
|
},
|
||||||
|
NoKeyRegistered,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn try_chunked_uploading(
|
||||||
|
registry: Option<String>,
|
||||||
|
token: Option<String>,
|
||||||
|
package: &wasmer_toml::Package,
|
||||||
|
manifest_string: &String,
|
||||||
|
license_file: &Option<String>,
|
||||||
|
readme: &Option<String>,
|
||||||
|
archive_name: &String,
|
||||||
|
archive_path: &PathBuf,
|
||||||
|
maybe_signature_data: &SignArchiveResult,
|
||||||
|
archived_data_size: u64,
|
||||||
|
quiet: bool,
|
||||||
|
) -> Result<(), anyhow::Error> {
|
||||||
|
let registry = match registry.as_ref() {
|
||||||
|
Some(s) => format_graphql(s),
|
||||||
|
None => {
|
||||||
|
let wasmer_dir = WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||||
|
|
||||||
|
let config = WasmerConfig::from_file(&wasmer_dir);
|
||||||
|
|
||||||
|
config
|
||||||
|
.map_err(|e| anyhow::anyhow!("{e}"))?
|
||||||
|
.registry
|
||||||
|
.get_current_registry()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let token = match token.as_ref() {
|
||||||
|
Some(s) => s.to_string(),
|
||||||
|
None => {
|
||||||
|
let wasmer_dir = WasmerConfig::get_wasmer_dir().map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||||
|
|
||||||
|
let config = WasmerConfig::from_file(&wasmer_dir);
|
||||||
|
|
||||||
|
config
|
||||||
|
.map_err(|e| anyhow::anyhow!("{e}"))?
|
||||||
|
.registry
|
||||||
|
.get_login_token_for_registry(®istry)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow::anyhow!("cannot publish package: not logged into registry {registry:?}")
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let maybe_signature_data = match maybe_signature_data {
|
||||||
|
SignArchiveResult::Ok {
|
||||||
|
public_key_id,
|
||||||
|
signature,
|
||||||
|
} => {
|
||||||
|
log::info!(
|
||||||
|
"Package successfully signed with public key: \"{}\"!",
|
||||||
|
&public_key_id
|
||||||
|
);
|
||||||
|
Some(publish_package_mutation_chunked::InputSignature {
|
||||||
|
public_key_key_id: public_key_id.to_string(),
|
||||||
|
data: signature.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
SignArchiveResult::NoKeyRegistered => {
|
||||||
|
// TODO: uncomment this when we actually want users to start using it
|
||||||
|
//warn!("Publishing package without a verifying signature. Consider registering a key pair with wapm");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !quiet {
|
||||||
|
println!("{} {} Uploading...", style("[1/2]").bold().dim(), UPLOAD);
|
||||||
|
}
|
||||||
|
|
||||||
|
let get_google_signed_url = GetSignedUrl::build_query(get_signed_url::Variables {
|
||||||
|
name: package.name.to_string(),
|
||||||
|
version: package.version.to_string(),
|
||||||
|
expires_after_seconds: Some(60 * 30),
|
||||||
|
});
|
||||||
|
|
||||||
|
let _response: get_signed_url::ResponseData =
|
||||||
|
execute_query_modifier_inner(®istry, &token, &get_google_signed_url, None, |f| f)?;
|
||||||
|
|
||||||
|
let url = _response.url.ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"could not get signed url for package {}@{}",
|
||||||
|
package.name,
|
||||||
|
package.version
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let signed_url = url.url;
|
||||||
|
let url = url::Url::parse(&signed_url).unwrap();
|
||||||
|
let client = reqwest::blocking::Client::builder()
|
||||||
|
.default_headers(reqwest::header::HeaderMap::default())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.post(url)
|
||||||
|
.header(reqwest::header::CONTENT_LENGTH, "0")
|
||||||
|
.header(reqwest::header::CONTENT_TYPE, "application/octet-stream")
|
||||||
|
.header("x-goog-resumable", "start");
|
||||||
|
|
||||||
|
let result = res.send().unwrap();
|
||||||
|
|
||||||
|
if result.status() != reqwest::StatusCode::from_u16(201).unwrap() {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Uploading package failed: got HTTP {:?} when uploading",
|
||||||
|
result.status()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let headers = result
|
||||||
|
.headers()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(k, v)| {
|
||||||
|
let k = k.to_string();
|
||||||
|
let v = v.to_str().ok()?.to_string();
|
||||||
|
Some((k.to_lowercase(), v))
|
||||||
|
})
|
||||||
|
.collect::<BTreeMap<_, _>>();
|
||||||
|
|
||||||
|
let session_uri = headers.get("location").unwrap().clone();
|
||||||
|
|
||||||
|
let total = archived_data_size;
|
||||||
|
|
||||||
|
// archive_path
|
||||||
|
let mut file = std::fs::OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.open(archive_path)
|
||||||
|
.map_err(|e| anyhow::anyhow!("cannot open archive {}: {e}", archive_path.display()))?;
|
||||||
|
|
||||||
|
let pb = ProgressBar::new(archived_data_size);
|
||||||
|
pb.set_style(ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta})")
|
||||||
|
.unwrap()
|
||||||
|
.with_key("eta", |state: &ProgressState, w: &mut dyn Write| {
|
||||||
|
write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap()
|
||||||
|
})
|
||||||
|
.progress_chars("#>-"));
|
||||||
|
|
||||||
|
let chunk_size = 1_048_576; // 1MB - 315s / 100MB
|
||||||
|
let mut file_pointer = 0;
|
||||||
|
|
||||||
|
let mut reader = std::io::BufReader::with_capacity(chunk_size, &mut file);
|
||||||
|
|
||||||
|
let client = reqwest::blocking::Client::builder()
|
||||||
|
.default_headers(reqwest::header::HeaderMap::default())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
while let Some(chunk) = reader.fill_buf().ok().map(|s| s.to_vec()) {
|
||||||
|
let n = chunk.len();
|
||||||
|
|
||||||
|
if chunk.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = file_pointer;
|
||||||
|
let end = file_pointer + chunk.len().saturating_sub(1);
|
||||||
|
let content_range = format!("bytes {start}-{end}/{total}");
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.put(&session_uri)
|
||||||
|
.header(reqwest::header::CONTENT_TYPE, "application/octet-stream")
|
||||||
|
.header(reqwest::header::CONTENT_LENGTH, format!("{}", chunk.len()))
|
||||||
|
.header("Content-Range".to_string(), content_range)
|
||||||
|
.body(chunk.to_vec());
|
||||||
|
|
||||||
|
pb.set_position(file_pointer as u64);
|
||||||
|
|
||||||
|
res.send()
|
||||||
|
.map(|response| response.error_for_status())
|
||||||
|
.map_err(|e| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"cannot send request to {session_uri} (chunk {}..{}): {e}",
|
||||||
|
file_pointer,
|
||||||
|
file_pointer + chunk_size
|
||||||
|
)
|
||||||
|
})??;
|
||||||
|
|
||||||
|
if n < chunk_size {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.consume(n);
|
||||||
|
file_pointer += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
pb.finish_and_clear();
|
||||||
|
|
||||||
|
if !quiet {
|
||||||
|
println!("{} {}Publishing...", style("[2/2]").bold().dim(), PACKAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
let q =
|
||||||
|
PublishPackageMutationChunked::build_query(publish_package_mutation_chunked::Variables {
|
||||||
|
name: package.name.to_string(),
|
||||||
|
version: package.version.to_string(),
|
||||||
|
description: package.description.clone(),
|
||||||
|
manifest: manifest_string.to_string(),
|
||||||
|
license: package.license.clone(),
|
||||||
|
license_file: license_file.to_owned(),
|
||||||
|
readme: readme.to_owned(),
|
||||||
|
repository: package.repository.clone(),
|
||||||
|
homepage: package.homepage.clone(),
|
||||||
|
file_name: Some(archive_name.to_string()),
|
||||||
|
signature: maybe_signature_data,
|
||||||
|
signed_url: Some(signed_url),
|
||||||
|
});
|
||||||
|
|
||||||
|
let _response: publish_package_mutation_chunked::ResponseData =
|
||||||
|
crate::graphql::execute_query(®istry, &token, &q)?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Successfully published package `{}@{}`",
|
||||||
|
package.name, package.version
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{graphql::execute_query, PartialWapmConfig};
|
use crate::graphql::execute_query;
|
||||||
use graphql_client::GraphQLQuery;
|
use graphql_client::GraphQLQuery;
|
||||||
|
|
||||||
#[derive(GraphQLQuery)]
|
#[derive(GraphQLQuery)]
|
||||||
@ -9,12 +9,7 @@ use graphql_client::GraphQLQuery;
|
|||||||
)]
|
)]
|
||||||
struct WhoAmIQuery;
|
struct WhoAmIQuery;
|
||||||
|
|
||||||
pub fn get_username(#[cfg(test)] test_name: &str) -> anyhow::Result<Option<String>> {
|
pub fn get_username(registry: &str) -> anyhow::Result<Option<String>> {
|
||||||
#[cfg(test)]
|
|
||||||
let config = PartialWapmConfig::from_file(test_name).map_err(|e| anyhow::anyhow!("{e}"))?;
|
|
||||||
#[cfg(not(test))]
|
|
||||||
let config = PartialWapmConfig::from_file().map_err(|e| anyhow::anyhow!("{e}"))?;
|
|
||||||
let registry = &config.registry.get_current_registry();
|
|
||||||
let q = WhoAmIQuery::build_query(who_am_i_query::Variables {});
|
let q = WhoAmIQuery::build_query(who_am_i_query::Variables {});
|
||||||
let response: who_am_i_query::ResponseData = execute_query(registry, "", &q)?;
|
let response: who_am_i_query::ResponseData = execute_query(registry, "", &q)?;
|
||||||
Ok(response.viewer.map(|viewer| viewer.username))
|
Ok(response.viewer.map(|viewer| viewer.username))
|
||||||
|
@ -15,7 +15,7 @@ thiserror = "1"
|
|||||||
tracing = { version = "0.1" }
|
tracing = { version = "0.1" }
|
||||||
typetag = { version = "0.1", optional = true }
|
typetag = { version = "0.1", optional = true }
|
||||||
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
|
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
|
||||||
webc = { version = "0.4.1", optional = true }
|
webc = { version = "4.0.0", optional = true }
|
||||||
slab = { version = "0.4" }
|
slab = { version = "0.4" }
|
||||||
derivative = "2.2.0"
|
derivative = "2.2.0"
|
||||||
anyhow = { version = "1.0.66", optional = true }
|
anyhow = { version = "1.0.66", optional = true }
|
||||||
|
@ -7,12 +7,8 @@
|
|||||||
use more_asserts::assert_le;
|
use more_asserts::assert_le;
|
||||||
use more_asserts::assert_lt;
|
use more_asserts::assert_lt;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Read;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
#[cfg(feature = "tracing")]
|
|
||||||
use tracing::trace;
|
|
||||||
|
|
||||||
/// Round `size` up to the nearest multiple of `page_size`.
|
/// Round `size` up to the nearest multiple of `page_size`.
|
||||||
fn round_up_to_page_size(size: usize, page_size: usize) -> usize {
|
fn round_up_to_page_size(size: usize, page_size: usize) -> usize {
|
||||||
@ -28,35 +24,8 @@ pub struct Mmap {
|
|||||||
// `unsafe impl`. This type is sendable across threads and shareable since
|
// `unsafe impl`. This type is sendable across threads and shareable since
|
||||||
// the coordination all happens at the OS layer.
|
// the coordination all happens at the OS layer.
|
||||||
ptr: usize,
|
ptr: usize,
|
||||||
len: usize,
|
total_size: usize,
|
||||||
// Backing file that will be closed when the memory mapping goes out of scope
|
accessible_size: usize,
|
||||||
fd: FdGuard,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct FdGuard(pub i32);
|
|
||||||
|
|
||||||
impl Default for FdGuard {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self(-1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for FdGuard {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
unsafe { Self(libc::dup(self.0)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for FdGuard {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if self.0 >= 0 {
|
|
||||||
unsafe {
|
|
||||||
libc::close(self.0);
|
|
||||||
}
|
|
||||||
self.0 = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mmap {
|
impl Mmap {
|
||||||
@ -68,8 +37,8 @@ impl Mmap {
|
|||||||
let empty = Vec::<u8>::new();
|
let empty = Vec::<u8>::new();
|
||||||
Self {
|
Self {
|
||||||
ptr: empty.as_ptr() as usize,
|
ptr: empty.as_ptr() as usize,
|
||||||
len: 0,
|
total_size: 0,
|
||||||
fd: FdGuard::default(),
|
accessible_size: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,28 +68,6 @@ impl Mmap {
|
|||||||
return Ok(Self::new());
|
return Ok(Self::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open a temporary file (which is used for swapping)
|
|
||||||
let fd = unsafe {
|
|
||||||
let file = libc::tmpfile();
|
|
||||||
if file.is_null() {
|
|
||||||
return Err(format!(
|
|
||||||
"failed to create temporary file - {}",
|
|
||||||
io::Error::last_os_error()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
FdGuard(libc::fileno(file))
|
|
||||||
};
|
|
||||||
|
|
||||||
// First we initialize it with zeros
|
|
||||||
unsafe {
|
|
||||||
if libc::ftruncate(fd.0, mapping_size as libc::off_t) < 0 {
|
|
||||||
return Err("could not truncate tmpfile".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the flags
|
|
||||||
let flags = libc::MAP_FILE | libc::MAP_SHARED;
|
|
||||||
|
|
||||||
Ok(if accessible_size == mapping_size {
|
Ok(if accessible_size == mapping_size {
|
||||||
// Allocate a single read-write region at once.
|
// Allocate a single read-write region at once.
|
||||||
let ptr = unsafe {
|
let ptr = unsafe {
|
||||||
@ -128,8 +75,8 @@ impl Mmap {
|
|||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
mapping_size,
|
mapping_size,
|
||||||
libc::PROT_READ | libc::PROT_WRITE,
|
libc::PROT_READ | libc::PROT_WRITE,
|
||||||
flags,
|
libc::MAP_PRIVATE | libc::MAP_ANON,
|
||||||
fd.0,
|
-1,
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@ -139,8 +86,8 @@ impl Mmap {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
ptr: ptr as usize,
|
ptr: ptr as usize,
|
||||||
len: mapping_size,
|
total_size: mapping_size,
|
||||||
fd,
|
accessible_size,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Reserve the mapping size.
|
// Reserve the mapping size.
|
||||||
@ -149,8 +96,8 @@ impl Mmap {
|
|||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
mapping_size,
|
mapping_size,
|
||||||
libc::PROT_NONE,
|
libc::PROT_NONE,
|
||||||
flags,
|
libc::MAP_PRIVATE | libc::MAP_ANON,
|
||||||
fd.0,
|
-1,
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@ -160,8 +107,8 @@ impl Mmap {
|
|||||||
|
|
||||||
let mut result = Self {
|
let mut result = Self {
|
||||||
ptr: ptr as usize,
|
ptr: ptr as usize,
|
||||||
len: mapping_size,
|
total_size: mapping_size,
|
||||||
fd,
|
accessible_size,
|
||||||
};
|
};
|
||||||
|
|
||||||
if accessible_size != 0 {
|
if accessible_size != 0 {
|
||||||
@ -194,7 +141,8 @@ impl Mmap {
|
|||||||
if mapping_size == 0 {
|
if mapping_size == 0 {
|
||||||
return Ok(Self::new());
|
return Ok(Self::new());
|
||||||
}
|
}
|
||||||
if accessible_size == mapping_size {
|
|
||||||
|
Ok(if accessible_size == mapping_size {
|
||||||
// Allocate a single read-write region at once.
|
// Allocate a single read-write region at once.
|
||||||
let ptr = unsafe {
|
let ptr = unsafe {
|
||||||
VirtualAlloc(
|
VirtualAlloc(
|
||||||
@ -208,11 +156,10 @@ impl Mmap {
|
|||||||
return Err(io::Error::last_os_error().to_string());
|
return Err(io::Error::last_os_error().to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Self {
|
||||||
ptr: ptr as usize,
|
ptr: ptr as usize,
|
||||||
len: mapping_size,
|
len: mapping_size,
|
||||||
fd: FdGuard::default(),
|
}
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
// Reserve the mapping size.
|
// Reserve the mapping size.
|
||||||
let ptr =
|
let ptr =
|
||||||
@ -224,7 +171,6 @@ impl Mmap {
|
|||||||
let mut result = Self {
|
let mut result = Self {
|
||||||
ptr: ptr as usize,
|
ptr: ptr as usize,
|
||||||
len: mapping_size,
|
len: mapping_size,
|
||||||
fd: FdGuard::default(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if accessible_size != 0 {
|
if accessible_size != 0 {
|
||||||
@ -232,8 +178,8 @@ impl Mmap {
|
|||||||
result.make_accessible(0, accessible_size)?;
|
result.make_accessible(0, accessible_size)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result)
|
result
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make the memory starting at `start` and extending for `len` bytes accessible.
|
/// Make the memory starting at `start` and extending for `len` bytes accessible.
|
||||||
@ -244,8 +190,8 @@ impl Mmap {
|
|||||||
let page_size = region::page::size();
|
let page_size = region::page::size();
|
||||||
assert_eq!(start & (page_size - 1), 0);
|
assert_eq!(start & (page_size - 1), 0);
|
||||||
assert_eq!(len & (page_size - 1), 0);
|
assert_eq!(len & (page_size - 1), 0);
|
||||||
assert_lt!(len, self.len);
|
assert_lt!(len, self.total_size);
|
||||||
assert_lt!(start, self.len - len);
|
assert_lt!(start, self.total_size - len);
|
||||||
|
|
||||||
// Commit the accessible size.
|
// Commit the accessible size.
|
||||||
let ptr = self.ptr as *const u8;
|
let ptr = self.ptr as *const u8;
|
||||||
@ -287,12 +233,22 @@ impl Mmap {
|
|||||||
|
|
||||||
/// Return the allocated memory as a slice of u8.
|
/// Return the allocated memory as a slice of u8.
|
||||||
pub fn as_slice(&self) -> &[u8] {
|
pub fn as_slice(&self) -> &[u8] {
|
||||||
unsafe { slice::from_raw_parts(self.ptr as *const u8, self.len) }
|
unsafe { slice::from_raw_parts(self.ptr as *const u8, self.total_size) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the allocated memory as a slice of u8.
|
||||||
|
pub fn as_slice_accessible(&self) -> &[u8] {
|
||||||
|
unsafe { slice::from_raw_parts(self.ptr as *const u8, self.accessible_size) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the allocated memory as a mutable slice of u8.
|
/// Return the allocated memory as a mutable slice of u8.
|
||||||
pub fn as_mut_slice(&mut self) -> &mut [u8] {
|
pub fn as_mut_slice(&mut self) -> &mut [u8] {
|
||||||
unsafe { slice::from_raw_parts_mut(self.ptr as *mut u8, self.len) }
|
unsafe { slice::from_raw_parts_mut(self.ptr as *mut u8, self.total_size) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the allocated memory as a mutable slice of u8.
|
||||||
|
pub fn as_mut_slice_accessible(&mut self) -> &mut [u8] {
|
||||||
|
unsafe { slice::from_raw_parts_mut(self.ptr as *mut u8, self.accessible_size) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the allocated memory as a pointer to u8.
|
/// Return the allocated memory as a pointer to u8.
|
||||||
@ -307,7 +263,7 @@ impl Mmap {
|
|||||||
|
|
||||||
/// Return the length of the allocated memory.
|
/// Return the length of the allocated memory.
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.len
|
self.total_size
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return whether any memory has been allocated.
|
/// Return whether any memory has been allocated.
|
||||||
@ -315,117 +271,20 @@ impl Mmap {
|
|||||||
self.len() == 0
|
self.len() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copies the memory to a new swap file (using copy-on-write if available)
|
/// Duplicate in a new memory mapping.
|
||||||
#[cfg(not(target_os = "windows"))]
|
pub fn duplicate(&mut self, _size_hint: Option<usize>) -> Result<Self, String> {
|
||||||
pub fn duplicate(&mut self, hint_used: Option<usize>) -> Result<Self, String> {
|
let mut new = Self::accessible_reserved(self.accessible_size, self.total_size)?;
|
||||||
// Empty memory is an edge case
|
new.as_mut_slice_accessible()
|
||||||
|
.copy_from_slice(self.as_slice_accessible());
|
||||||
use std::os::unix::prelude::FromRawFd;
|
Ok(new)
|
||||||
if self.len == 0 {
|
|
||||||
return Ok(Self::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
// First we sync all the data to the backing file
|
|
||||||
unsafe {
|
|
||||||
libc::fsync(self.fd.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open a new temporary file (which is used for swapping for the forked memory)
|
|
||||||
let fd = unsafe {
|
|
||||||
let file = libc::tmpfile();
|
|
||||||
if file.is_null() {
|
|
||||||
return Err(format!(
|
|
||||||
"failed to create temporary file - {}",
|
|
||||||
io::Error::last_os_error()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
FdGuard(libc::fileno(file))
|
|
||||||
};
|
|
||||||
|
|
||||||
// Attempt to do a shallow copy (needs a backing file system that supports it)
|
|
||||||
unsafe {
|
|
||||||
if libc::ioctl(fd.0, 0x94, 9, self.fd.0) != 0
|
|
||||||
// FICLONE
|
|
||||||
{
|
|
||||||
#[cfg(feature = "tracing")]
|
|
||||||
trace!("memory copy started");
|
|
||||||
|
|
||||||
// Determine host much to copy
|
|
||||||
let len = match hint_used {
|
|
||||||
Some(a) => a,
|
|
||||||
None => self.len,
|
|
||||||
};
|
|
||||||
|
|
||||||
// The shallow copy failed so we have to do it the hard way
|
|
||||||
|
|
||||||
let mut source = std::fs::File::from_raw_fd(self.fd.0);
|
|
||||||
let mut out = std::fs::File::from_raw_fd(fd.0);
|
|
||||||
copy_file_range(&mut source, 0, &mut out, 0, len)
|
|
||||||
.map_err(|err| format!("Could not copy memory: {err}"))?;
|
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
|
||||||
trace!("memory copy finished (size={})", len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the flags
|
|
||||||
let flags = libc::MAP_FILE | libc::MAP_SHARED;
|
|
||||||
|
|
||||||
// Allocate a single read-write region at once.
|
|
||||||
let ptr = unsafe {
|
|
||||||
libc::mmap(
|
|
||||||
ptr::null_mut(),
|
|
||||||
self.len,
|
|
||||||
libc::PROT_READ | libc::PROT_WRITE,
|
|
||||||
flags,
|
|
||||||
fd.0,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
if ptr as isize == -1_isize {
|
|
||||||
return Err(io::Error::last_os_error().to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
ptr: ptr as usize,
|
|
||||||
len: self.len,
|
|
||||||
fd,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copies the memory to a new swap file (using copy-on-write if available)
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn duplicate(&mut self, hint_used: Option<usize>) -> Result<Self, String> {
|
|
||||||
// Create a new memory which we will copy to
|
|
||||||
let new_mmap = Self::with_at_least(self.len)?;
|
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
|
||||||
trace!("memory copy started");
|
|
||||||
|
|
||||||
// Determine host much to copy
|
|
||||||
let len = match hint_used {
|
|
||||||
Some(a) => a,
|
|
||||||
None => self.len,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Copy the data to the new memory
|
|
||||||
let dst = new_mmap.ptr as *mut u8;
|
|
||||||
let src = self.ptr as *const u8;
|
|
||||||
unsafe {
|
|
||||||
std::ptr::copy_nonoverlapping(src, dst, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
|
||||||
trace!("memory copy finished (size={})", len);
|
|
||||||
Ok(new_mmap)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Mmap {
|
impl Drop for Mmap {
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.len != 0 {
|
if self.total_size != 0 {
|
||||||
let r = unsafe { libc::munmap(self.ptr as *mut libc::c_void, self.len) };
|
let r = unsafe { libc::munmap(self.ptr as *mut libc::c_void, self.total_size) };
|
||||||
assert_eq!(r, 0, "munmap failed: {}", io::Error::last_os_error());
|
assert_eq!(r, 0, "munmap failed: {}", io::Error::last_os_error());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -447,54 +306,6 @@ fn _assert() {
|
|||||||
_assert_send_sync::<Mmap>();
|
_assert_send_sync::<Mmap>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copy a range of a file to another file.
|
|
||||||
// We could also use libc::copy_file_range on some systems, but it's
|
|
||||||
// hard to do this because it is not available on many libc implementations.
|
|
||||||
// (not on Mac OS, musl, ...)
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
fn copy_file_range(
|
|
||||||
source: &mut std::fs::File,
|
|
||||||
source_offset: u64,
|
|
||||||
out: &mut std::fs::File,
|
|
||||||
out_offset: u64,
|
|
||||||
len: usize,
|
|
||||||
) -> Result<(), std::io::Error> {
|
|
||||||
use std::io::{Seek, SeekFrom};
|
|
||||||
|
|
||||||
let source_original_pos = source.stream_position()?;
|
|
||||||
source.seek(SeekFrom::Start(source_offset))?;
|
|
||||||
|
|
||||||
// TODO: don't cast with as
|
|
||||||
|
|
||||||
let out_original_pos = out.stream_position()?;
|
|
||||||
out.seek(SeekFrom::Start(out_offset))?;
|
|
||||||
|
|
||||||
// TODO: don't do this horrible "triple buffering" below".
|
|
||||||
// let mut reader = std::io::BufReader::new(source);
|
|
||||||
|
|
||||||
// TODO: larger buffer?
|
|
||||||
let mut buffer = vec![0u8; 4096];
|
|
||||||
|
|
||||||
let mut to_read = len;
|
|
||||||
while to_read > 0 {
|
|
||||||
let chunk_size = std::cmp::min(to_read, buffer.len());
|
|
||||||
let read = source.read(&mut buffer[0..chunk_size])?;
|
|
||||||
out.write_all(&buffer[0..read])?;
|
|
||||||
to_read -= read;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Need to read the last chunk.
|
|
||||||
out.flush()?;
|
|
||||||
|
|
||||||
// Restore files to original position.
|
|
||||||
source.seek(SeekFrom::Start(source_original_pos))?;
|
|
||||||
out.flush()?;
|
|
||||||
out.sync_data()?;
|
|
||||||
out.seek(SeekFrom::Start(out_original_pos))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -506,59 +317,4 @@ mod tests {
|
|||||||
assert_eq!(round_up_to_page_size(4096, 4096), 4096);
|
assert_eq!(round_up_to_page_size(4096, 4096), 4096);
|
||||||
assert_eq!(round_up_to_page_size(4097, 4096), 8192);
|
assert_eq!(round_up_to_page_size(4097, 4096), 8192);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
#[test]
|
|
||||||
fn test_copy_file_range() -> Result<(), std::io::Error> {
|
|
||||||
// I know tempfile:: exists, but this doesn't bring in an extra
|
|
||||||
// dependency.
|
|
||||||
|
|
||||||
use std::{fs::OpenOptions, io::Seek};
|
|
||||||
|
|
||||||
let dir = std::env::temp_dir().join("wasmer/copy_file_range");
|
|
||||||
if dir.is_dir() {
|
|
||||||
std::fs::remove_dir_all(&dir).unwrap()
|
|
||||||
}
|
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
|
||||||
|
|
||||||
let pa = dir.join("a");
|
|
||||||
let pb = dir.join("b");
|
|
||||||
|
|
||||||
let data: Vec<u8> = (0..100).collect();
|
|
||||||
let mut a = OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.write(true)
|
|
||||||
.create_new(true)
|
|
||||||
.open(&pa)
|
|
||||||
.unwrap();
|
|
||||||
a.write_all(&data).unwrap();
|
|
||||||
|
|
||||||
let datb: Vec<u8> = (100..200).collect();
|
|
||||||
let mut b = OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.write(true)
|
|
||||||
.create_new(true)
|
|
||||||
.open(&pb)
|
|
||||||
.unwrap();
|
|
||||||
b.write_all(&datb).unwrap();
|
|
||||||
|
|
||||||
a.seek(io::SeekFrom::Start(30)).unwrap();
|
|
||||||
b.seek(io::SeekFrom::Start(99)).unwrap();
|
|
||||||
copy_file_range(&mut a, 10, &mut b, 40, 15).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(a.stream_position().unwrap(), 30);
|
|
||||||
assert_eq!(b.stream_position().unwrap(), 99);
|
|
||||||
|
|
||||||
b.seek(io::SeekFrom::Start(0)).unwrap();
|
|
||||||
let mut out = Vec::new();
|
|
||||||
let len = b.read_to_end(&mut out).unwrap();
|
|
||||||
assert_eq!(len, 100);
|
|
||||||
assert_eq!(out[0..40], datb[0..40]);
|
|
||||||
assert_eq!(out[40..55], data[10..25]);
|
|
||||||
assert_eq!(out[55..100], datb[55..100]);
|
|
||||||
|
|
||||||
// TODO: needs more variant tests, but this is enough for now.
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ bincode = { version = "1.3" }
|
|||||||
chrono = { version = "^0.4", default-features = false, features = [ "wasmbind", "std", "clock" ], optional = true }
|
chrono = { version = "^0.4", default-features = false, features = [ "wasmbind", "std", "clock" ], optional = true }
|
||||||
derivative = { version = "^2" }
|
derivative = { version = "^2" }
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
webc = { version = "0.4.1", default-features = false, features = ["std", "mmap"] }
|
webc = { version = "4.0.0", optional = true, default-features = false, features = ["std", "mmap"] }
|
||||||
serde_cbor = { version = "0.11.2", optional = true }
|
serde_cbor = { version = "0.11.2", optional = true }
|
||||||
anyhow = { version = "1.0.66" }
|
anyhow = { version = "1.0.66" }
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
|
24
lib/wasm-interface/Cargo.toml
Normal file
24
lib/wasm-interface/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "wasmer-wasm-interface"
|
||||||
|
version = "3.1.0"
|
||||||
|
authors = ["The Wasmer Engineering Team <engineering@wasmer.io>"]
|
||||||
|
edition = "2018"
|
||||||
|
repository = "https://github.com/wasmerio/wapm-cli"
|
||||||
|
description = "WASM Interface definition and parser"
|
||||||
|
readme = "README.md"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bincode = { version = "1", optional = true }
|
||||||
|
either = "1.5"
|
||||||
|
nom = "5"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
wasmparser = { version = "0.51.4", optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
wat = "1.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
validation = ["wasmparser"]
|
||||||
|
binary_encode = ["bincode"]
|
||||||
|
default = ["validation"]
|
88
lib/wasm-interface/README.md
Normal file
88
lib/wasm-interface/README.md
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
# Wasm Interface
|
||||||
|
|
||||||
|
This is an experimental crate for validating the imports and exports of a WebAssembly module.
|
||||||
|
|
||||||
|
For the time being, Wasm Interface provides:
|
||||||
|
|
||||||
|
- a convenient text format for specifying the requirements of Wasm modules
|
||||||
|
- a convenient way to compose interfaces safely (it ensures no conflicts (duplicates are allowed but they must agree))
|
||||||
|
- validation that the modules meet the requirements
|
||||||
|
|
||||||
|
## Syntax example
|
||||||
|
|
||||||
|
Here's the interface for the current version of [WASI](https://github.com/WebAssembly/WASI):
|
||||||
|
|
||||||
|
```lisp
|
||||||
|
(interface "wasi_unstable"
|
||||||
|
;; Here's a bunch of function imports!
|
||||||
|
(func (import "wasi_unstable" "args_get") (param i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "args_sizes_get") (param i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "clock_res_get") (param i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "clock_time_get") (param i32 i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "environ_get") (param i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "environ_sizes_get") (param i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_advise") (param i32 i64 i64 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_allocate") (param i32 i64 i64) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_close") (param i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_datasync") (param i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_fdstat_get") (param i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_fdstat_set_flags") (param i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_fdstat_set_rights") (param i32 i64 i64) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_filestat_get") (param i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_filestat_set_size") (param i32 i64) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_filestat_set_times") (param i32 i64 i64 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_pread") (param i32 i32 i32 i64 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_prestat_get") (param i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_prestat_dir_name") (param i32 i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_pwrite") (param i32 i32 i32 i64 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_read") (param i32 i32 i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_readdir") (param i32 i32 i32 i64 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_renumber") (param i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_seek") (param i32 i64 i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_sync") (param i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_tell") (param i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "fd_write") (param i32 i32 i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "path_create_directory") (param i32 i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "path_filestat_get") (param i32 i32 i32 i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "path_filestat_set_times") (param i32 i32 i32 i32 i64 i64 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "path_link") (param i32 i32 i32 i32 i32 i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "path_open") (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "path_readlink") (param i32 i32 i32 i32 i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "path_remove_directory") (param i32 i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "path_rename") (param i32 i32 i32 i32 i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "path_symlink") (param i32 i32 i32 i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "path_unlink_file") (param i32 i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "poll_oneoff") (param i32 i32 i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "proc_exit") (param i32))
|
||||||
|
(func (import "wasi_unstable" "proc_raise") (param i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "random_get") (param i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "sched_yield") (result i32))
|
||||||
|
(func (import "wasi_unstable" "sock_recv") (param i32 i32 i32 i32 i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "sock_send") (param i32 i32 i32 i32 i32) (result i32))
|
||||||
|
(func (import "wasi_unstable" "sock_shutdown") (param i32 i32) (result i32))
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- multiple `assert-import` and `assert-export` declarations are allowed.
|
||||||
|
- comments (starts with `;` and ends with a newline) and whitespace are valid between any tokens
|
||||||
|
|
||||||
|
## Semantics
|
||||||
|
|
||||||
|
All imports used by the module must be specified in the interface.
|
||||||
|
|
||||||
|
All exports in the interface must be exported by the module.
|
||||||
|
|
||||||
|
Thus the module may have additional exports than the interface or fewer imports than the interface specifies and be considered valid.
|
||||||
|
|
||||||
|
|
||||||
|
## Misc
|
||||||
|
|
||||||
|
Wasm Interface serves a slightly different purpose than the proposed WebIDL for Wasm standard, but may be replaced by it in the future if things change.
|
||||||
|
|
||||||
|
Due to an issue with nested closures in Rust, `wasm-interface` can't both compile on stable and have good error reporting. This is being fixed and `wasm-interface` will be updated to have better error handling.
|
||||||
|
|
||||||
|
See the `parser.rs` file for a comment containing the grammar in a BNF style.
|
||||||
|
|
||||||
|
Suggestions, contributions, and thoughts welcome! This is an experiment in the early stages, but we hope to work with the wider community and develop this in cooperation with all interested parties.
|
205
lib/wasm-interface/src/interface.rs
Normal file
205
lib/wasm-interface/src/interface.rs
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
//! The definition of a WASM interface
|
||||||
|
|
||||||
|
use crate::interface_matcher::InterfaceMatcher;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::{hash_map::Entry, HashMap, HashSet};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||||
|
pub struct Interface {
|
||||||
|
/// The name the interface gave itself
|
||||||
|
pub name: Option<String>,
|
||||||
|
/// Things that the module can import
|
||||||
|
pub imports: HashMap<(String, String), Import>,
|
||||||
|
/// Things that the module must export
|
||||||
|
pub exports: HashMap<String, Export>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interface {
|
||||||
|
pub fn merge(&self, other: Interface) -> Result<Interface, String> {
|
||||||
|
let mut base = self.clone();
|
||||||
|
|
||||||
|
for (key, val) in other.imports {
|
||||||
|
match base.imports.entry(key) {
|
||||||
|
Entry::Occupied(e) if *e.get() != val => {
|
||||||
|
let (namespace, name) = e.key();
|
||||||
|
let original_value = e.get();
|
||||||
|
return Err(format!("Conflict detected: the import \"{namespace}\" \"{name}\" was found but the definitions were different: {original_value:?} {val:?}"));
|
||||||
|
}
|
||||||
|
Entry::Occupied(_) => {
|
||||||
|
// it's okay for the imported items to be the same.
|
||||||
|
}
|
||||||
|
Entry::Vacant(e) => {
|
||||||
|
e.insert(val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (key, val) in other.exports {
|
||||||
|
match base.exports.entry(key) {
|
||||||
|
Entry::Occupied(e) if *e.get() != val => {
|
||||||
|
let name = e.key();
|
||||||
|
let original_value = e.get();
|
||||||
|
return Err(format!("Conflict detected: the key \"{name}\" was found in exports but the definitions were different: {original_value:?} {val:?}"));
|
||||||
|
}
|
||||||
|
Entry::Occupied(_) => {
|
||||||
|
// it's okay for the exported items to be the same.
|
||||||
|
}
|
||||||
|
Entry::Vacant(e) => {
|
||||||
|
e.insert(val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_interface_matcher(&self) -> InterfaceMatcher {
|
||||||
|
let mut namespaces = HashSet::new();
|
||||||
|
let mut namespace_imports: HashMap<String, HashSet<Import>> =
|
||||||
|
HashMap::with_capacity(self.imports.len());
|
||||||
|
let mut exports = HashSet::with_capacity(self.exports.len());
|
||||||
|
|
||||||
|
for (_, import) in self.imports.iter() {
|
||||||
|
match import {
|
||||||
|
Import::Func { namespace, .. } | Import::Global { namespace, .. } => {
|
||||||
|
if !namespaces.contains(namespace) {
|
||||||
|
namespaces.insert(namespace.clone());
|
||||||
|
}
|
||||||
|
let ni = namespace_imports.entry(namespace.clone()).or_default();
|
||||||
|
ni.insert(import.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (_, export) in self.exports.iter() {
|
||||||
|
exports.insert(export.clone());
|
||||||
|
}
|
||||||
|
InterfaceMatcher {
|
||||||
|
namespaces,
|
||||||
|
namespace_imports,
|
||||||
|
exports,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum Import {
|
||||||
|
Func {
|
||||||
|
namespace: String,
|
||||||
|
name: String,
|
||||||
|
params: Vec<WasmType>,
|
||||||
|
result: Vec<WasmType>,
|
||||||
|
},
|
||||||
|
Global {
|
||||||
|
namespace: String,
|
||||||
|
name: String,
|
||||||
|
var_type: WasmType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Import {
|
||||||
|
pub fn format_key(ns: &str, name: &str) -> (String, String) {
|
||||||
|
(ns.to_string(), name.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the key used to look this import up in the Interface's import hashmap
|
||||||
|
pub fn get_key(&self) -> (String, String) {
|
||||||
|
match self {
|
||||||
|
Import::Func {
|
||||||
|
namespace, name, ..
|
||||||
|
}
|
||||||
|
| Import::Global {
|
||||||
|
namespace, name, ..
|
||||||
|
} => Self::format_key(namespace, name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum Export {
|
||||||
|
Func {
|
||||||
|
name: String,
|
||||||
|
params: Vec<WasmType>,
|
||||||
|
result: Vec<WasmType>,
|
||||||
|
},
|
||||||
|
Global {
|
||||||
|
name: String,
|
||||||
|
var_type: WasmType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Export {
|
||||||
|
pub fn format_key(name: &str) -> String {
|
||||||
|
name.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the key used to look this export up in the Interface's export hashmap
|
||||||
|
pub fn get_key(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Export::Func { name, .. } | Export::Global { name, .. } => Self::format_key(name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Primitive wasm type
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum WasmType {
|
||||||
|
I32,
|
||||||
|
I64,
|
||||||
|
F32,
|
||||||
|
F64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for WasmType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
WasmType::I32 => "i32",
|
||||||
|
WasmType::I64 => "i64",
|
||||||
|
WasmType::F32 => "f32",
|
||||||
|
WasmType::F64 => "f64",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::parser;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn merging_works() {
|
||||||
|
let interface1_src =
|
||||||
|
r#"(interface (func (import "env" "plus_one") (param i32) (result i32)))"#;
|
||||||
|
let interface2_src =
|
||||||
|
r#"(interface (func (import "env" "plus_one") (param i64) (result i64)))"#;
|
||||||
|
let interface3_src =
|
||||||
|
r#"(interface (func (import "env" "times_two") (param i64) (result i64)))"#;
|
||||||
|
let interface4_src =
|
||||||
|
r#"(interface (func (import "env" "times_two") (param i64 i64) (result i64)))"#;
|
||||||
|
let interface5_src = r#"(interface (func (export "empty_bank_account") (param) (result)))"#;
|
||||||
|
let interface6_src =
|
||||||
|
r#"(interface (func (export "empty_bank_account") (param) (result i64)))"#;
|
||||||
|
|
||||||
|
let interface1 = parser::parse_interface(interface1_src).unwrap();
|
||||||
|
let interface2 = parser::parse_interface(interface2_src).unwrap();
|
||||||
|
let interface3 = parser::parse_interface(interface3_src).unwrap();
|
||||||
|
let interface4 = parser::parse_interface(interface4_src).unwrap();
|
||||||
|
let interface5 = parser::parse_interface(interface5_src).unwrap();
|
||||||
|
let interface6 = parser::parse_interface(interface6_src).unwrap();
|
||||||
|
|
||||||
|
assert!(interface1.merge(interface2.clone()).is_err());
|
||||||
|
assert!(interface2.merge(interface1.clone()).is_err());
|
||||||
|
assert!(interface1.merge(interface3.clone()).is_ok());
|
||||||
|
assert!(interface2.merge(interface3.clone()).is_ok());
|
||||||
|
assert!(interface3.merge(interface2).is_ok());
|
||||||
|
assert!(
|
||||||
|
interface1.merge(interface1.clone()).is_ok(),
|
||||||
|
"exact matches are accepted"
|
||||||
|
);
|
||||||
|
assert!(interface3.merge(interface4).is_err());
|
||||||
|
assert!(interface5.merge(interface5.clone()).is_ok());
|
||||||
|
assert!(interface5.merge(interface6).is_err());
|
||||||
|
}
|
||||||
|
}
|
29
lib/wasm-interface/src/interface_matcher.rs
Normal file
29
lib/wasm-interface/src/interface_matcher.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
use crate::interface::{Export, Import};
|
||||||
|
|
||||||
|
/// A struct containing data for more efficient matching.
|
||||||
|
///
|
||||||
|
/// An ideal use case for this is to parse [`Interface`]s at compile time,
|
||||||
|
/// create [`InterfaceMatcher`]s, and store them as bytes so that they
|
||||||
|
/// can be efficiently loaded at runtime for matching.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||||
|
pub struct InterfaceMatcher {
|
||||||
|
pub namespaces: HashSet<String>,
|
||||||
|
pub namespace_imports: HashMap<String, HashSet<Import>>,
|
||||||
|
pub exports: HashSet<Export>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "binary_encode")]
|
||||||
|
impl InterfaceMatcher {
|
||||||
|
/// Store the matcher as bytes to avoid reparsing
|
||||||
|
fn into_bytes(&self) -> Vec<u8> {
|
||||||
|
bincode::serialize(self).expect("Could not serialize InterfaceMatcher")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the matcher from bytes to avoid reparsing
|
||||||
|
fn from_bytes(bytes: &[u8]) -> Option<Self> {
|
||||||
|
bincode::deserialize(bytes).ok()
|
||||||
|
}
|
||||||
|
}
|
13
lib/wasm-interface/src/lib.rs
Normal file
13
lib/wasm-interface/src/lib.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#![type_length_limit = "5795522"]
|
||||||
|
//! Definition and parsing of wasm interfaces
|
||||||
|
//!
|
||||||
|
//! wasm interfaces ensure wasm modules conform to a specific shape
|
||||||
|
//! they do this by asserting on the imports and exports of the module.
|
||||||
|
|
||||||
|
pub mod interface;
|
||||||
|
pub mod interface_matcher;
|
||||||
|
pub mod parser;
|
||||||
|
#[cfg(feature = "validation")]
|
||||||
|
pub mod validate;
|
||||||
|
|
||||||
|
pub use interface::*;
|
596
lib/wasm-interface/src/parser.rs
Normal file
596
lib/wasm-interface/src/parser.rs
Normal file
@ -0,0 +1,596 @@
|
|||||||
|
//! Parsers to get a wasm interface from text
|
||||||
|
//!
|
||||||
|
//! The grammar of the text format is:
|
||||||
|
//! interface = "(" interface name? interface-entry* ")"
|
||||||
|
//! interface-entry = func | global
|
||||||
|
//!
|
||||||
|
//! func = import-fn | export-fn
|
||||||
|
//! global = import-global | export-global
|
||||||
|
//!
|
||||||
|
//! import-fn = "(" "func" import-id param-list? result-list? ")"
|
||||||
|
//! import-global = "(" "global" import-id type-decl ")"
|
||||||
|
//! import-id = "(" "import" namespace name ")"
|
||||||
|
//!
|
||||||
|
//! export-fn = "(" "func" export-id param-list? result-list? ")"
|
||||||
|
//! export-global = "(" "global" export-id type-decl ")"
|
||||||
|
//! export-id = "(" export name ")"
|
||||||
|
//!
|
||||||
|
//! param-list = "(" param type* ")"
|
||||||
|
//! result-list = "(" result type* ")"
|
||||||
|
//! type-decl = "(" "type" type ")"
|
||||||
|
//! namespace = "\"" identifier "\""
|
||||||
|
//! name = "\"" identifier "\""
|
||||||
|
//! identifier = any character that's not a whitespace character or an open or close parenthesis
|
||||||
|
//! type = "i32" | "i64" | "f32" | "f64"
|
||||||
|
//!
|
||||||
|
//! + means 1 or more
|
||||||
|
//! * means 0 or more
|
||||||
|
//! ? means 0 or 1
|
||||||
|
//! | means "or"
|
||||||
|
//! "\"" means one `"` character
|
||||||
|
//!
|
||||||
|
//! comments start with a `;` character and go until a newline `\n` character is reached
|
||||||
|
//! comments and whitespace are valid between any tokens
|
||||||
|
|
||||||
|
use either::Either;
|
||||||
|
use nom::{
|
||||||
|
branch::*,
|
||||||
|
bytes::complete::{escaped, is_not, tag},
|
||||||
|
character::complete::{char, multispace0, multispace1, one_of},
|
||||||
|
combinator::*,
|
||||||
|
error::context,
|
||||||
|
multi::many0,
|
||||||
|
sequence::{delimited, preceded, tuple},
|
||||||
|
IResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::interface::*;
|
||||||
|
|
||||||
|
/// Some example input:
|
||||||
|
/// (interface "example_interface"
|
||||||
|
/// (func (import "ns" "name") (param f64 i32) (result f64 i32))
|
||||||
|
/// (func (export "name") (param f64 i32) (result f64 i32))
|
||||||
|
/// (global (import "ns" "name") (type f64)))
|
||||||
|
pub fn parse_interface(mut input: &str) -> Result<Interface, String> {
|
||||||
|
let mut interface = Interface::default();
|
||||||
|
let interface_inner = preceded(
|
||||||
|
tag("interface"),
|
||||||
|
tuple((
|
||||||
|
opt(preceded(space_comments, identifier)),
|
||||||
|
many0(parse_func_or_global),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
let interface_parser = preceded(space_comments, s_exp(interface_inner));
|
||||||
|
|
||||||
|
if let Result::Ok((inp, (sig_id, out))) = interface_parser(input) {
|
||||||
|
interface.name = sig_id.map(|s_id| s_id.to_string());
|
||||||
|
|
||||||
|
for entry in out.into_iter() {
|
||||||
|
match entry {
|
||||||
|
Either::Left(import) => {
|
||||||
|
if let Some(dup) = interface.imports.insert(import.get_key(), import) {
|
||||||
|
return Err(format!("Duplicate import found {:?}", dup));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Either::Right(export) => {
|
||||||
|
if let Some(dup) = interface.exports.insert(export.get_key(), export) {
|
||||||
|
return Err(format!("Duplicate export found {:?}", dup));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input = inp;
|
||||||
|
}
|
||||||
|
// catch trailing comments and spaces
|
||||||
|
if let Ok((inp, _)) = space_comments(input) {
|
||||||
|
input = inp;
|
||||||
|
}
|
||||||
|
if !input.is_empty() {
|
||||||
|
Err(format!("Could not parse remaining input: {}", input))
|
||||||
|
} else {
|
||||||
|
Ok(interface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_comment(input: &str) -> IResult<&str, ()> {
|
||||||
|
map(
|
||||||
|
preceded(multispace0, preceded(char(';'), many0(is_not("\n")))),
|
||||||
|
|_| (),
|
||||||
|
)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes spaces and comments
|
||||||
|
/// comments must terminate with a new line character
|
||||||
|
fn space_comments<'a>(mut input: &'a str) -> IResult<&'a str, ()> {
|
||||||
|
let mut space_found = true;
|
||||||
|
let mut comment_found = true;
|
||||||
|
while space_found || comment_found {
|
||||||
|
let space: IResult<&'a str, _> = multispace1(input);
|
||||||
|
space_found = if let Result::Ok((inp, _)) = space {
|
||||||
|
input = inp;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
comment_found = if let Result::Ok((inp, _)) = parse_comment(input) {
|
||||||
|
input = inp;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok((input, ()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A quoted identifier, must be valid UTF8
|
||||||
|
fn identifier(input: &str) -> IResult<&str, &str> {
|
||||||
|
let name_inner = escaped(is_not("\"\\"), '\\', one_of("\"n\\"));
|
||||||
|
context("identifier", delimited(char('"'), name_inner, char('"')))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a wasm primitive type
|
||||||
|
fn wasm_type(input: &str) -> IResult<&str, WasmType> {
|
||||||
|
let i32_tag = map(tag("i32"), |_| WasmType::I32);
|
||||||
|
let i64_tag = map(tag("i64"), |_| WasmType::I64);
|
||||||
|
let f32_tag = map(tag("f32"), |_| WasmType::F32);
|
||||||
|
let f64_tag = map(tag("f64"), |_| WasmType::F64);
|
||||||
|
|
||||||
|
alt((i32_tag, i64_tag, f32_tag, f64_tag))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses an S-expression
|
||||||
|
fn s_exp<'a, O1, F>(inner: F) -> impl Fn(&'a str) -> IResult<&'a str, O1>
|
||||||
|
where
|
||||||
|
F: Fn(&'a str) -> IResult<&'a str, O1>,
|
||||||
|
{
|
||||||
|
delimited(
|
||||||
|
char('('),
|
||||||
|
preceded(space_comments, inner),
|
||||||
|
preceded(space_comments, char(')')),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_func_or_global(input: &str) -> IResult<&str, Either<Import, Export>> {
|
||||||
|
preceded(space_comments, alt((func, global)))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (func (import "ns" "name") (param f64 i32) (result f64 i32))
|
||||||
|
/// (func (export "name") (param f64 i32) (result f64 i32))
|
||||||
|
fn func(input: &str) -> IResult<&str, Either<Import, Export>> {
|
||||||
|
let param_list_inner = preceded(tag("param"), many0(preceded(space_comments, wasm_type)));
|
||||||
|
let param_list = opt(s_exp(param_list_inner));
|
||||||
|
let result_list_inner = preceded(tag("result"), many0(preceded(space_comments, wasm_type)));
|
||||||
|
let result_list = opt(s_exp(result_list_inner));
|
||||||
|
let import_id_inner = preceded(
|
||||||
|
tag("import"),
|
||||||
|
tuple((
|
||||||
|
preceded(space_comments, identifier),
|
||||||
|
preceded(space_comments, identifier),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
let export_id_inner = preceded(tag("export"), preceded(space_comments, identifier));
|
||||||
|
let func_id_inner = alt((
|
||||||
|
map(import_id_inner, |(ns, name)| {
|
||||||
|
Either::Left((ns.to_string(), name.to_string()))
|
||||||
|
}),
|
||||||
|
map(export_id_inner, |name| Either::Right(name.to_string())),
|
||||||
|
));
|
||||||
|
let func_id = s_exp(func_id_inner);
|
||||||
|
let func_import_inner = context(
|
||||||
|
"func import inner",
|
||||||
|
preceded(
|
||||||
|
tag("func"),
|
||||||
|
map(
|
||||||
|
tuple((
|
||||||
|
preceded(space_comments, func_id),
|
||||||
|
preceded(space_comments, param_list),
|
||||||
|
preceded(space_comments, result_list),
|
||||||
|
)),
|
||||||
|
|(func_id, pl, rl)| match func_id {
|
||||||
|
Either::Left((ns, name)) => Either::Left(Import::Func {
|
||||||
|
namespace: ns,
|
||||||
|
name,
|
||||||
|
params: pl.unwrap_or_default(),
|
||||||
|
result: rl.unwrap_or_default(),
|
||||||
|
}),
|
||||||
|
Either::Right(name) => Either::Right(Export::Func {
|
||||||
|
name,
|
||||||
|
params: pl.unwrap_or_default(),
|
||||||
|
result: rl.unwrap_or_default(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
s_exp(func_import_inner)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (global (import "ns" "name") (type f64))
|
||||||
|
/// (global (export "name") (type f64))
|
||||||
|
fn global(input: &str) -> IResult<&str, Either<Import, Export>> {
|
||||||
|
let global_type_inner = preceded(tag("type"), preceded(space_comments, wasm_type));
|
||||||
|
let type_s_exp = s_exp(global_type_inner);
|
||||||
|
let export_inner = preceded(tag("export"), preceded(space_comments, identifier));
|
||||||
|
let import_inner = preceded(
|
||||||
|
tag("import"),
|
||||||
|
tuple((
|
||||||
|
preceded(space_comments, identifier),
|
||||||
|
preceded(space_comments, identifier),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
let global_id_inner = alt((
|
||||||
|
map(import_inner, |(ns, name)| {
|
||||||
|
Either::Left(Import::Global {
|
||||||
|
namespace: ns.to_string(),
|
||||||
|
name: name.to_string(),
|
||||||
|
// placeholder type, overwritten in `global_inner`
|
||||||
|
var_type: WasmType::I32,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
map(export_inner, |name| {
|
||||||
|
Either::Right(Export::Global {
|
||||||
|
name: name.to_string(),
|
||||||
|
// placeholder type, overwritten in `global_inner`
|
||||||
|
var_type: WasmType::I32,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
let global_id = s_exp(global_id_inner);
|
||||||
|
let global_inner = context(
|
||||||
|
"global inner",
|
||||||
|
preceded(
|
||||||
|
tag("global"),
|
||||||
|
map(
|
||||||
|
tuple((
|
||||||
|
preceded(space_comments, global_id),
|
||||||
|
preceded(space_comments, type_s_exp),
|
||||||
|
)),
|
||||||
|
|(import_or_export, var_type)| match import_or_export {
|
||||||
|
Either::Left(Import::Global {
|
||||||
|
namespace, name, ..
|
||||||
|
}) => Either::Left(Import::Global {
|
||||||
|
namespace,
|
||||||
|
name,
|
||||||
|
var_type,
|
||||||
|
}),
|
||||||
|
Either::Right(Export::Global { name, .. }) => {
|
||||||
|
Either::Right(Export::Global { name, var_type })
|
||||||
|
}
|
||||||
|
_ => unreachable!("Invalid value interonally in parse global function"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
s_exp(global_inner)(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_wasm_type() {
|
||||||
|
let i32_res = wasm_type("i32").unwrap();
|
||||||
|
assert_eq!(i32_res, ("", WasmType::I32));
|
||||||
|
let i64_res = wasm_type("i64").unwrap();
|
||||||
|
assert_eq!(i64_res, ("", WasmType::I64));
|
||||||
|
let f32_res = wasm_type("f32").unwrap();
|
||||||
|
assert_eq!(f32_res, ("", WasmType::F32));
|
||||||
|
let f64_res = wasm_type("f64").unwrap();
|
||||||
|
assert_eq!(f64_res, ("", WasmType::F64));
|
||||||
|
|
||||||
|
assert!(wasm_type("i128").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_identifier() {
|
||||||
|
let inner_str = "柴は可愛すぎるだと思います";
|
||||||
|
let input = format!("\"{}\"", &inner_str);
|
||||||
|
let parse_res = identifier(&input).unwrap();
|
||||||
|
assert_eq!(parse_res, ("", inner_str))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_global_import() {
|
||||||
|
let parse_res = global(r#"(global (import "env" "length") (type i32))"#)
|
||||||
|
.ok()
|
||||||
|
.and_then(|(a, b)| Some((a, b.left()?)))
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
parse_res,
|
||||||
|
(
|
||||||
|
"",
|
||||||
|
Import::Global {
|
||||||
|
namespace: "env".to_string(),
|
||||||
|
name: "length".to_string(),
|
||||||
|
var_type: WasmType::I32,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_global_export() {
|
||||||
|
let parse_res = global(r#"(global (export "length") (type i32))"#)
|
||||||
|
.ok()
|
||||||
|
.and_then(|(a, b)| Some((a, b.right()?)))
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
parse_res,
|
||||||
|
(
|
||||||
|
"",
|
||||||
|
Export::Global {
|
||||||
|
name: "length".to_string(),
|
||||||
|
var_type: WasmType::I32,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_func_import() {
|
||||||
|
let parse_res = func(r#"(func (import "ns" "name") (param f64 i32) (result f64 i32))"#)
|
||||||
|
.ok()
|
||||||
|
.and_then(|(a, b)| Some((a, b.left()?)))
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
parse_res,
|
||||||
|
(
|
||||||
|
"",
|
||||||
|
Import::Func {
|
||||||
|
namespace: "ns".to_string(),
|
||||||
|
name: "name".to_string(),
|
||||||
|
params: vec![WasmType::F64, WasmType::I32],
|
||||||
|
result: vec![WasmType::F64, WasmType::I32],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_func_export() {
|
||||||
|
let parse_res = func(r#"(func (export "name") (param f64 i32) (result f64 i32))"#)
|
||||||
|
.ok()
|
||||||
|
.and_then(|(a, b)| Some((a, b.right()?)))
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
parse_res,
|
||||||
|
(
|
||||||
|
"",
|
||||||
|
Export::Func {
|
||||||
|
name: "name".to_string(),
|
||||||
|
params: vec![WasmType::F64, WasmType::I32],
|
||||||
|
result: vec![WasmType::F64, WasmType::I32],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let parse_res = func(r#"(func (export "name"))"#)
|
||||||
|
.ok()
|
||||||
|
.and_then(|(a, b)| Some((a, b.right()?)))
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
parse_res,
|
||||||
|
(
|
||||||
|
"",
|
||||||
|
Export::Func {
|
||||||
|
name: "name".to_string(),
|
||||||
|
params: vec![],
|
||||||
|
result: vec![],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_imports_test() {
|
||||||
|
let parse_imports = |in_str| {
|
||||||
|
many0(parse_func_or_global)(in_str)
|
||||||
|
.map(|(a, b)| {
|
||||||
|
(
|
||||||
|
a,
|
||||||
|
b.into_iter().filter_map(|x| x.left()).collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
let parse_res =
|
||||||
|
parse_imports(r#"(func (import "ns" "name") (param f64 i32) (result f64 i32))"#);
|
||||||
|
assert_eq!(
|
||||||
|
parse_res,
|
||||||
|
(
|
||||||
|
"",
|
||||||
|
vec![Import::Func {
|
||||||
|
namespace: "ns".to_string(),
|
||||||
|
name: "name".to_string(),
|
||||||
|
params: vec![WasmType::F64, WasmType::I32],
|
||||||
|
result: vec![WasmType::F64, WasmType::I32],
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let parse_res = parse_imports(
|
||||||
|
r#"(func (import "ns" "name")
|
||||||
|
(param f64 i32) (result f64 i32))
|
||||||
|
( global ( import "env" "length" ) ( type
|
||||||
|
;; i32 is the best type
|
||||||
|
i32 )
|
||||||
|
)
|
||||||
|
(func (import "ns" "name2") (param f32
|
||||||
|
i64)
|
||||||
|
;; The return value comes next
|
||||||
|
(
|
||||||
|
result
|
||||||
|
f64
|
||||||
|
i32
|
||||||
|
)
|
||||||
|
)"#,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_res,
|
||||||
|
(
|
||||||
|
"",
|
||||||
|
vec![
|
||||||
|
Import::Func {
|
||||||
|
namespace: "ns".to_string(),
|
||||||
|
name: "name".to_string(),
|
||||||
|
params: vec![WasmType::F64, WasmType::I32],
|
||||||
|
result: vec![WasmType::F64, WasmType::I32],
|
||||||
|
},
|
||||||
|
Import::Global {
|
||||||
|
namespace: "env".to_string(),
|
||||||
|
name: "length".to_string(),
|
||||||
|
var_type: WasmType::I32,
|
||||||
|
},
|
||||||
|
Import::Func {
|
||||||
|
namespace: "ns".to_string(),
|
||||||
|
name: "name2".to_string(),
|
||||||
|
params: vec![WasmType::F32, WasmType::I64],
|
||||||
|
result: vec![WasmType::F64, WasmType::I32],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn top_level_test() {
|
||||||
|
let parse_res = parse_interface(
|
||||||
|
r#" (interface
|
||||||
|
(func (import "ns" "name") (param f64 i32) (result f64 i32))
|
||||||
|
(func (export "name2") (param) (result i32))
|
||||||
|
(global (import "env" "length") (type f64)))"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let imports = vec![
|
||||||
|
Import::Func {
|
||||||
|
namespace: "ns".to_string(),
|
||||||
|
name: "name".to_string(),
|
||||||
|
params: vec![WasmType::F64, WasmType::I32],
|
||||||
|
result: vec![WasmType::F64, WasmType::I32],
|
||||||
|
},
|
||||||
|
Import::Global {
|
||||||
|
namespace: "env".to_string(),
|
||||||
|
name: "length".to_string(),
|
||||||
|
var_type: WasmType::F64,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let exports = vec![Export::Func {
|
||||||
|
name: "name2".to_string(),
|
||||||
|
params: vec![],
|
||||||
|
result: vec![WasmType::I32],
|
||||||
|
}];
|
||||||
|
let import_map = imports
|
||||||
|
.into_iter()
|
||||||
|
.map(|entry| (entry.get_key(), entry))
|
||||||
|
.collect::<HashMap<(String, String), Import>>();
|
||||||
|
let export_map = exports
|
||||||
|
.into_iter()
|
||||||
|
.map(|entry| (entry.get_key(), entry))
|
||||||
|
.collect::<HashMap<String, Export>>();
|
||||||
|
assert_eq!(
|
||||||
|
parse_res,
|
||||||
|
Interface {
|
||||||
|
name: None,
|
||||||
|
imports: import_map,
|
||||||
|
exports: export_map,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn duplicates_not_allowed() {
|
||||||
|
let parse_res = parse_interface(
|
||||||
|
r#" (interface "sig_name" (func (import "ns" "name") (param f64 i32) (result f64 i32))
|
||||||
|
; test comment
|
||||||
|
;; hello
|
||||||
|
(func (import "ns" "name") (param) (result i32))
|
||||||
|
(global (export "length") (type f64)))
|
||||||
|
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(parse_res.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_comment_space_parsing() {
|
||||||
|
let parse_res = space_comments(" ").unwrap();
|
||||||
|
assert_eq!(parse_res, ("", ()));
|
||||||
|
let parse_res = space_comments("").unwrap();
|
||||||
|
assert_eq!(parse_res, ("", ()));
|
||||||
|
let parse_res = space_comments("; hello\n").unwrap();
|
||||||
|
assert_eq!(parse_res, ("", ()));
|
||||||
|
let parse_res = space_comments("abc").unwrap();
|
||||||
|
assert_eq!(parse_res, ("abc", ()));
|
||||||
|
let parse_res = space_comments("\n ; hello\n ").unwrap();
|
||||||
|
assert_eq!(parse_res, ("", ()));
|
||||||
|
let parse_res = space_comments("\n ; hello\n ; abc\n\n ; hello\n").unwrap();
|
||||||
|
assert_eq!(parse_res, ("", ()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_param_elision() {
|
||||||
|
let parse_res = parse_interface(
|
||||||
|
r#" (interface "interface_name" (func (import "ns" "name") (result f64 i32))
|
||||||
|
(func (export "name")))
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let imports = vec![Import::Func {
|
||||||
|
namespace: "ns".to_string(),
|
||||||
|
name: "name".to_string(),
|
||||||
|
params: vec![],
|
||||||
|
result: vec![WasmType::F64, WasmType::I32],
|
||||||
|
}];
|
||||||
|
let exports = vec![Export::Func {
|
||||||
|
name: "name".to_string(),
|
||||||
|
params: vec![],
|
||||||
|
result: vec![],
|
||||||
|
}];
|
||||||
|
let import_map = imports
|
||||||
|
.into_iter()
|
||||||
|
.map(|entry| (entry.get_key(), entry))
|
||||||
|
.collect::<HashMap<(String, String), Import>>();
|
||||||
|
let export_map = exports
|
||||||
|
.into_iter()
|
||||||
|
.map(|entry| (entry.get_key(), entry))
|
||||||
|
.collect::<HashMap<String, Export>>();
|
||||||
|
assert_eq!(
|
||||||
|
parse_res,
|
||||||
|
Interface {
|
||||||
|
name: Some("interface_name".to_string()),
|
||||||
|
imports: import_map,
|
||||||
|
exports: export_map,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn typo_gets_caught() {
|
||||||
|
let interface_src = r#"
|
||||||
|
(interface "interface_id"
|
||||||
|
(func (import "env" "do_panic") (params i32 i64))
|
||||||
|
(global (import "length") (type i32)))"#;
|
||||||
|
let result = parse_interface(interface_src);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_trailing_spaces_on_interface() {
|
||||||
|
let parse_res = parse_interface(
|
||||||
|
r#" (interface "really_good_interface" (func (import "ns" "name") (param f64 i32) (result f64 i32))
|
||||||
|
; test comment
|
||||||
|
;; hello
|
||||||
|
(global (import "ns" "length") (type f64))
|
||||||
|
)
|
||||||
|
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(parse_res.is_ok());
|
||||||
|
}
|
||||||
|
}
|
465
lib/wasm-interface/src/validate.rs
Normal file
465
lib/wasm-interface/src/validate.rs
Normal file
@ -0,0 +1,465 @@
|
|||||||
|
//! Validate a wasm module given a interface.
|
||||||
|
//!
|
||||||
|
//! This checks that all imports are specified in the interface and that their types
|
||||||
|
//! are correct, as well as that all exports that the interface expects are exported
|
||||||
|
//! by the module and that their types are correct.
|
||||||
|
|
||||||
|
use crate::{Export, Import, Interface, WasmType};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use wasmparser::{ExternalKind, FuncType, GlobalType, ImportSectionEntryType};
|
||||||
|
|
||||||
|
pub fn validate_wasm_and_report_errors(
|
||||||
|
wasm: &[u8],
|
||||||
|
interface: &Interface,
|
||||||
|
) -> Result<(), WasmValidationError> {
|
||||||
|
use wasmparser::WasmDecoder;
|
||||||
|
|
||||||
|
let mut errors: Vec<String> = vec![];
|
||||||
|
let mut import_fns: HashMap<(String, String), u32> = HashMap::new();
|
||||||
|
let mut export_fns: HashMap<String, u32> = HashMap::new();
|
||||||
|
let mut export_globals: HashMap<String, u32> = HashMap::new();
|
||||||
|
let mut type_defs: Vec<FuncType> = vec![];
|
||||||
|
let mut global_types: Vec<GlobalType> = vec![];
|
||||||
|
let mut fn_sigs: Vec<u32> = vec![];
|
||||||
|
|
||||||
|
let mut parser = wasmparser::ValidatingParser::new(wasm, None);
|
||||||
|
loop {
|
||||||
|
let state = parser.read();
|
||||||
|
match state {
|
||||||
|
wasmparser::ParserState::EndWasm => break,
|
||||||
|
wasmparser::ParserState::Error(e) => {
|
||||||
|
return Err(WasmValidationError::InvalidWasm {
|
||||||
|
error: format!("{}", e),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
wasmparser::ParserState::ImportSectionEntry {
|
||||||
|
module,
|
||||||
|
field,
|
||||||
|
ref ty,
|
||||||
|
} => match ty {
|
||||||
|
ImportSectionEntryType::Function(idx) => {
|
||||||
|
import_fns.insert(Import::format_key(module, field), *idx);
|
||||||
|
fn_sigs.push(*idx);
|
||||||
|
}
|
||||||
|
ImportSectionEntryType::Global(GlobalType { content_type, .. }) => {
|
||||||
|
let global_type =
|
||||||
|
wasmparser_type_into_wasm_type(*content_type).map_err(|err| {
|
||||||
|
WasmValidationError::UnsupportedType {
|
||||||
|
error: format!(
|
||||||
|
"Invalid type found in import \"{}\" \"{}\": {}",
|
||||||
|
module, field, err
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
if let Some(val) = interface.imports.get(&Import::format_key(module, field)) {
|
||||||
|
if let Import::Global { var_type, .. } = val {
|
||||||
|
if *var_type != global_type {
|
||||||
|
errors.push(format!(
|
||||||
|
"Invalid type on Global \"{}\". Expected {} found {}",
|
||||||
|
field, var_type, global_type
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errors.push(format!(
|
||||||
|
"Invalid import type. Expected Global, found {:?}",
|
||||||
|
val
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errors.push(format!(
|
||||||
|
"Global import \"{}\" not found in the specified interface",
|
||||||
|
field
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
wasmparser::ParserState::ExportSectionEntry {
|
||||||
|
field,
|
||||||
|
index,
|
||||||
|
ref kind,
|
||||||
|
} => match kind {
|
||||||
|
ExternalKind::Function => {
|
||||||
|
export_fns.insert(Export::format_key(field), *index);
|
||||||
|
}
|
||||||
|
ExternalKind::Global => {
|
||||||
|
export_globals.insert(Export::format_key(field), *index);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
wasmparser::ParserState::BeginGlobalSectionEntry(gt) => {
|
||||||
|
global_types.push(*gt);
|
||||||
|
}
|
||||||
|
wasmparser::ParserState::TypeSectionEntry(ft) => {
|
||||||
|
type_defs.push(ft.clone());
|
||||||
|
}
|
||||||
|
wasmparser::ParserState::FunctionSectionEntry(n) => {
|
||||||
|
fn_sigs.push(*n);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_imports(&import_fns, &type_defs, interface, &mut errors);
|
||||||
|
validate_export_fns(&export_fns, &type_defs, &fn_sigs, interface, &mut errors);
|
||||||
|
validate_export_globals(&export_globals, &global_types, interface, &mut errors);
|
||||||
|
|
||||||
|
if errors.is_empty() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(WasmValidationError::InterfaceViolated { errors })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates the import functions, checking the name and type against the given
|
||||||
|
/// `Interface`
|
||||||
|
fn validate_imports(
|
||||||
|
import_fns: &HashMap<(String, String), u32>,
|
||||||
|
type_defs: &[FuncType],
|
||||||
|
interface: &Interface,
|
||||||
|
errors: &mut Vec<String>,
|
||||||
|
) {
|
||||||
|
for (key, val) in import_fns.iter() {
|
||||||
|
if let Some(interface_def) = interface.imports.get(key) {
|
||||||
|
let type_sig = if let Some(v) = type_defs.get(*val as usize) {
|
||||||
|
v
|
||||||
|
} else {
|
||||||
|
errors.push(format!(
|
||||||
|
"Use of undeclared function reference \"{}\" in import function \"{}\" \"{}\"",
|
||||||
|
val, key.0, key.1
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if let Import::Func { params, result, .. } = interface_def {
|
||||||
|
debug_assert!(type_sig.form == wasmparser::Type::Func);
|
||||||
|
for (i, param) in type_sig
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(wasmparser_type_into_wasm_type)
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
match param {
|
||||||
|
Ok(t) => {
|
||||||
|
if params.get(i).is_none() {
|
||||||
|
errors.push(format!("Found {} args but the interface only expects {} for imported function \"{}\" \"{}\"", i, params.len(), &key.0, &key.1));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if t != params[i] {
|
||||||
|
errors.push(format!(
|
||||||
|
"Type mismatch in params in imported func \"{}\" \"{}\": argument {}, expected {} found {}",
|
||||||
|
&key.0, &key.1, i + 1, params[i], t
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => errors.push(format!(
|
||||||
|
"Invalid type in func \"{}\" \"{}\": {}",
|
||||||
|
&key.0, &key.1, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i, ret) in type_sig
|
||||||
|
.returns
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(wasmparser_type_into_wasm_type)
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
match ret {
|
||||||
|
Ok(t) => {
|
||||||
|
if result.get(i).is_none() {
|
||||||
|
errors.push(format!("Found {} returns but the interface only expects {} for imported function \"{}\" \"{}\"", i, params.len(), &key.0, &key.1));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if t != result[i] {
|
||||||
|
errors.push(format!(
|
||||||
|
"Type mismatch in returns in func \"{}\" \"{}\", return {}, expected {} found {}",
|
||||||
|
&key.0, &key.1, i + 1, params[i], t
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => errors.push(format!(
|
||||||
|
"Invalid type in func \"{}\" \"{}\": {}",
|
||||||
|
&key.0, &key.1, e
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we didn't find the import at all in the interface
|
||||||
|
// TODO: improve error messages by including type information
|
||||||
|
errors.push(format!("Missing import \"{}\" \"{}\"", key.0, key.1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates the export functions, checking the name and type against the given
|
||||||
|
/// `Interface`
|
||||||
|
fn validate_export_fns(
|
||||||
|
export_fns: &HashMap<String, u32>,
|
||||||
|
type_defs: &[FuncType],
|
||||||
|
fn_sigs: &Vec<u32>,
|
||||||
|
interface: &Interface,
|
||||||
|
errors: &mut Vec<String>,
|
||||||
|
) {
|
||||||
|
'export_loop: for (key, val) in export_fns.iter() {
|
||||||
|
if let Some(interface_def) = interface.exports.get(key) {
|
||||||
|
let type_sig = if let Some(type_idx) = fn_sigs.get(*val as usize) {
|
||||||
|
if let Some(v) = type_defs.get(*type_idx as usize) {
|
||||||
|
v
|
||||||
|
} else {
|
||||||
|
errors.push(format!(
|
||||||
|
"Export \"{}\" refers to type \"{}\" but only {} types were found",
|
||||||
|
&key,
|
||||||
|
type_idx,
|
||||||
|
fn_sigs.len()
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errors.push(format!(
|
||||||
|
"Use of undeclared function reference \"{}\" in export \"{}\"",
|
||||||
|
val, &key
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if let Export::Func { params, result, .. } = interface_def {
|
||||||
|
debug_assert!(type_sig.form == wasmparser::Type::Func);
|
||||||
|
for (i, param) in type_sig
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(wasmparser_type_into_wasm_type)
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
match param {
|
||||||
|
Ok(t) => {
|
||||||
|
if params.get(i).is_none() {
|
||||||
|
errors.push(format!("Found {} args but the interface only expects {} for exported function \"{}\"", type_sig.params.len(), params.len(), &key));
|
||||||
|
continue 'export_loop;
|
||||||
|
}
|
||||||
|
if t != params[i] {
|
||||||
|
errors.push(format!(
|
||||||
|
"Type mismatch in params in exported func \"{}\": in argument {}, expected {} found {}",
|
||||||
|
&key, i + 1, params[i], t
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => errors
|
||||||
|
.push(format!("Invalid type in exported func \"{}\": {}", &key, e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i, ret) in type_sig
|
||||||
|
.returns
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(wasmparser_type_into_wasm_type)
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
match ret {
|
||||||
|
Ok(t) => {
|
||||||
|
if result.get(i).is_none() {
|
||||||
|
errors.push(format!("Found {} returns but the interface only expects {} for exported function \"{}\"", i, params.len(), &key));
|
||||||
|
continue 'export_loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
if t != result[i] {
|
||||||
|
errors.push(format!(
|
||||||
|
"Type mismatch in returns in exported func \"{}\": in return {}, expected {} found {}",
|
||||||
|
&key, i + 1, result[i], t
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => errors
|
||||||
|
.push(format!("Invalid type in exported func \"{}\": {}", &key, e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates the export globals, checking the name and type against the given
|
||||||
|
/// `Interface`
|
||||||
|
fn validate_export_globals(
|
||||||
|
export_globals: &HashMap<String, u32>,
|
||||||
|
global_types: &Vec<GlobalType>,
|
||||||
|
interface: &Interface,
|
||||||
|
errors: &mut Vec<String>,
|
||||||
|
) {
|
||||||
|
for (key, val) in export_globals.iter() {
|
||||||
|
if let Some(Export::Global { var_type, .. }) = interface.exports.get(key) {
|
||||||
|
if global_types.get(*val as usize).is_none() {
|
||||||
|
errors.push(format!(
|
||||||
|
"Invalid wasm, expected {} global types, found {}",
|
||||||
|
val,
|
||||||
|
global_types.len()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
match wasmparser_type_into_wasm_type(global_types[*val as usize].content_type) {
|
||||||
|
Ok(t) => {
|
||||||
|
if *var_type != t {
|
||||||
|
errors.push(format!(
|
||||||
|
"Type mismatch in global export {}: expected {} found {}",
|
||||||
|
&key, var_type, t
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => errors.push(format!("In global export {}: {}", &key, e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts Wasmparser's type enum into wasm-interface's type enum
|
||||||
|
/// wasmparser's enum contains things which are invalid in many situations
|
||||||
|
///
|
||||||
|
/// Additionally wasmerparser containers more advanced types like references that
|
||||||
|
/// wasm-interface does not yet support
|
||||||
|
fn wasmparser_type_into_wasm_type(ty: wasmparser::Type) -> Result<WasmType, String> {
|
||||||
|
use wasmparser::Type;
|
||||||
|
Ok(match ty {
|
||||||
|
Type::I32 => WasmType::I32,
|
||||||
|
Type::I64 => WasmType::I64,
|
||||||
|
Type::F32 => WasmType::F32,
|
||||||
|
Type::F64 => WasmType::F64,
|
||||||
|
e => {
|
||||||
|
return Err(format!("Invalid type found: {:?}", e));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod validation_tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::parser;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn global_imports() {
|
||||||
|
const WAT: &str = r#"(module
|
||||||
|
(type $t0 (func (param i32 i64)))
|
||||||
|
(global $length (import "env" "length") i32)
|
||||||
|
(import "env" "do_panic" (func $do_panic (type $t0)))
|
||||||
|
)"#;
|
||||||
|
let wasm = wat::parse_str(WAT).unwrap();
|
||||||
|
|
||||||
|
let interface_src = r#"
|
||||||
|
(interface
|
||||||
|
(func (import "env" "do_panic") (param i32 i64))
|
||||||
|
(global (import "env" "length") (type i32)))"#;
|
||||||
|
let interface = parser::parse_interface(interface_src).unwrap();
|
||||||
|
|
||||||
|
let result = validate_wasm_and_report_errors(&wasm[..], &interface);
|
||||||
|
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
// Now set the global import type to mismatch the wasm
|
||||||
|
let interface_src = r#"
|
||||||
|
(interface
|
||||||
|
(func (import "env" "do_panic") (param i32 i64))
|
||||||
|
(global (import "env" "length") (type i64)))"#;
|
||||||
|
let interface = parser::parse_interface(interface_src).unwrap();
|
||||||
|
|
||||||
|
let result = validate_wasm_and_report_errors(&wasm[..], &interface);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"global import type mismatch causes an error"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now set the function import type to mismatch the wasm
|
||||||
|
let interface_src = r#"
|
||||||
|
(interface
|
||||||
|
(func (import "env" "do_panic") (param i64))
|
||||||
|
(global (import "env" "length") (type i32)))"#;
|
||||||
|
let interface = parser::parse_interface(interface_src).unwrap();
|
||||||
|
|
||||||
|
let result = validate_wasm_and_report_errors(&wasm[..], &interface);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"function import type mismatch causes an error"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now try with a module that has an import that the interface doesn't have
|
||||||
|
let interface_src = r#"
|
||||||
|
(interface
|
||||||
|
(func (import "env" "do_panic") (param i64))
|
||||||
|
(global (import "env" "length_plus_plus") (type i32)))"#;
|
||||||
|
let interface = parser::parse_interface(interface_src).unwrap();
|
||||||
|
|
||||||
|
let result = validate_wasm_and_report_errors(&wasm[..], &interface);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"all imports must be covered by the interface"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn global_exports() {
|
||||||
|
const WAT: &str = r#"(module
|
||||||
|
(func (export "as-set_local-first") (param i32) (result i32)
|
||||||
|
(nop) (i32.const 2) (set_local 0) (get_local 0))
|
||||||
|
(global (export "num_tries") i64 (i64.const 0))
|
||||||
|
)"#;
|
||||||
|
let wasm = wat::parse_str(WAT).unwrap();
|
||||||
|
|
||||||
|
let interface_src = r#"
|
||||||
|
(interface
|
||||||
|
(func (export "as-set_local-first") (param i32) (result i32))
|
||||||
|
(global (export "num_tries") (type i64)))"#;
|
||||||
|
let interface = parser::parse_interface(interface_src).unwrap();
|
||||||
|
|
||||||
|
let result = validate_wasm_and_report_errors(&wasm[..], &interface);
|
||||||
|
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
// Now set the global export type to mismatch the wasm
|
||||||
|
let interface_src = r#"
|
||||||
|
(interface
|
||||||
|
(func (export "as-set_local-first") (param i32) (result i32))
|
||||||
|
(global (export "num_tries") (type f32)))"#;
|
||||||
|
let interface = parser::parse_interface(interface_src).unwrap();
|
||||||
|
|
||||||
|
let result = validate_wasm_and_report_errors(&wasm[..], &interface);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"global export type mismatch causes an error"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now set the function export type to mismatch the wasm
|
||||||
|
let interface_src = r#"
|
||||||
|
(interface
|
||||||
|
(func (export "as-set_local-first") (param i64) (result i64))
|
||||||
|
(global (export "num_tries") (type i64)))"#;
|
||||||
|
let interface = parser::parse_interface(interface_src).unwrap();
|
||||||
|
|
||||||
|
let result = validate_wasm_and_report_errors(&wasm[..], &interface);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"function export type mismatch causes an error"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now try a interface that requires an export that the module doesn't have
|
||||||
|
let interface_src = r#"
|
||||||
|
(interface
|
||||||
|
(func (export "as-set_local-first") (param i64) (result i64))
|
||||||
|
(global (export "numb_trees") (type i64)))"#;
|
||||||
|
let interface = parser::parse_interface(interface_src).unwrap();
|
||||||
|
|
||||||
|
let result = validate_wasm_and_report_errors(&wasm[..], &interface);
|
||||||
|
|
||||||
|
assert!(result.is_err(), "missing a required export is an error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum WasmValidationError {
|
||||||
|
InvalidWasm { error: String },
|
||||||
|
InterfaceViolated { errors: Vec<String> },
|
||||||
|
UnsupportedType { error: String },
|
||||||
|
}
|
@ -419,6 +419,6 @@ fn large_number_local(mut config: crate::Config) -> Result<()> {
|
|||||||
.get_function("large_local")?
|
.get_function("large_local")?
|
||||||
.call(&mut store, &[])
|
.call(&mut store, &[])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(&Value::I64(1 as i64), result.get(0).unwrap());
|
assert_eq!(&Value::I64(1_i64), result.get(0).unwrap());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -246,11 +246,11 @@ fn static_host_function_without_env(config: crate::Config) -> anyhow::Result<()>
|
|||||||
let mut store = config.store();
|
let mut store = config.store();
|
||||||
|
|
||||||
fn f(a: i32, b: i64, c: f32, d: f64) -> (f64, f32, i64, i32) {
|
fn f(a: i32, b: i64, c: f32, d: f64) -> (f64, f32, i64, i32) {
|
||||||
(d * 4.0, c * 3.0, b * 2, a * 1)
|
(d * 4.0, c * 3.0, b * 2, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn f_ok(a: i32, b: i64, c: f32, d: f64) -> Result<(f64, f32, i64, i32), Infallible> {
|
fn f_ok(a: i32, b: i64, c: f32, d: f64) -> Result<(f64, f32, i64, i32), Infallible> {
|
||||||
Ok((d * 4.0, c * 3.0, b * 2, a * 1))
|
Ok((d * 4.0, c * 3.0, b * 2, a))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn long_f(
|
fn long_f(
|
||||||
@ -313,7 +313,7 @@ fn static_host_function_with_env(config: crate::Config) -> anyhow::Result<()> {
|
|||||||
assert_eq!(*guard, 100);
|
assert_eq!(*guard, 100);
|
||||||
*guard = 101;
|
*guard = 101;
|
||||||
|
|
||||||
(d * 4.0, c * 3.0, b * 2, a * 1)
|
(d * 4.0, c * 3.0, b * 2, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn f_ok(
|
fn f_ok(
|
||||||
@ -327,7 +327,7 @@ fn static_host_function_with_env(config: crate::Config) -> anyhow::Result<()> {
|
|||||||
assert_eq!(*guard, 100);
|
assert_eq!(*guard, 100);
|
||||||
*guard = 101;
|
*guard = 101;
|
||||||
|
|
||||||
Ok((d * 4.0, c * 3.0, b * 2, a * 1))
|
Ok((d * 4.0, c * 3.0, b * 2, a))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -401,7 +401,7 @@ fn dynamic_host_function_without_env(config: crate::Config) -> anyhow::Result<()
|
|||||||
Value::F64(values[3].unwrap_f64() * 4.0),
|
Value::F64(values[3].unwrap_f64() * 4.0),
|
||||||
Value::F32(values[2].unwrap_f32() * 3.0),
|
Value::F32(values[2].unwrap_f32() * 3.0),
|
||||||
Value::I64(values[1].unwrap_i64() * 2),
|
Value::I64(values[1].unwrap_i64() * 2),
|
||||||
Value::I32(values[0].unwrap_i32() * 1),
|
Value::I32(values[0].unwrap_i32()),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -457,7 +457,7 @@ fn dynamic_host_function_with_env(config: crate::Config) -> anyhow::Result<()> {
|
|||||||
Value::F64(values[3].unwrap_f64() * 4.0),
|
Value::F64(values[3].unwrap_f64() * 4.0),
|
||||||
Value::F32(values[2].unwrap_f32() * 3.0),
|
Value::F32(values[2].unwrap_f32() * 3.0),
|
||||||
Value::I64(values[1].unwrap_i64() * 2),
|
Value::I64(values[1].unwrap_i64() * 2),
|
||||||
Value::I32(values[0].unwrap_i32() * 1),
|
Value::I32(values[0].unwrap_i32()),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -16,6 +16,7 @@ serde = { version = "1.0.147", features = ["derive"] }
|
|||||||
insta = { version = "1.21.1", features = ["json"] }
|
insta = { version = "1.21.1", features = ["json"] }
|
||||||
md5 = "0.7.0"
|
md5 = "0.7.0"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
|
pretty_assertions = "1.3.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
365
tests/integration/cli/tests/config.rs
Normal file
365
tests/integration/cli/tests/config.rs
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
use anyhow::bail;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Command;
|
||||||
|
use wasmer_integration_tests_cli::get_wasmer_path;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wasmer_config_multiget() -> anyhow::Result<()> {
|
||||||
|
let bin_path = Path::new(env!("WASMER_DIR")).join("bin");
|
||||||
|
let include_path = Path::new(env!("WASMER_DIR")).join("include");
|
||||||
|
|
||||||
|
let bin = format!("{}", bin_path.display());
|
||||||
|
let include = format!("-I{}", include_path.display());
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("--bindir")
|
||||||
|
.arg("--cflags")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let lines = String::from_utf8_lossy(&output.stdout)
|
||||||
|
.lines()
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let expected = vec![bin, include];
|
||||||
|
|
||||||
|
assert_eq!(lines, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wasmer_config_error() -> anyhow::Result<()> {
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("--bindir")
|
||||||
|
.arg("--cflags")
|
||||||
|
.arg("--pkg-config")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let lines = String::from_utf8_lossy(&output.stderr)
|
||||||
|
.lines()
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let expected = vec![
|
||||||
|
"error: The argument '--bindir' cannot be used with '--pkg-config'",
|
||||||
|
"",
|
||||||
|
"USAGE:",
|
||||||
|
"wasmer config --bindir --cflags",
|
||||||
|
"",
|
||||||
|
"For more information try --help",
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(lines, expected);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn config_works() -> anyhow::Result<()> {
|
||||||
|
let bindir = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("--bindir")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let bin_path = Path::new(env!("WASMER_DIR")).join("bin");
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(bindir.stdout).unwrap(),
|
||||||
|
format!("{}\n", bin_path.display())
|
||||||
|
);
|
||||||
|
|
||||||
|
let bindir = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("--cflags")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let include_path = Path::new(env!("WASMER_DIR")).join("include");
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(bindir.stdout).unwrap(),
|
||||||
|
format!("-I{}\n", include_path.display())
|
||||||
|
);
|
||||||
|
|
||||||
|
let bindir = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("--includedir")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let include_path = Path::new(env!("WASMER_DIR")).join("include");
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(bindir.stdout).unwrap(),
|
||||||
|
format!("{}\n", include_path.display())
|
||||||
|
);
|
||||||
|
|
||||||
|
let bindir = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("--libdir")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let lib_path = Path::new(env!("WASMER_DIR")).join("lib");
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(bindir.stdout).unwrap(),
|
||||||
|
format!("{}\n", lib_path.display())
|
||||||
|
);
|
||||||
|
|
||||||
|
let bindir = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("--libs")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let lib_path = Path::new(env!("WASMER_DIR")).join("lib");
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(bindir.stdout).unwrap(),
|
||||||
|
format!("-L{} -lwasmer\n", lib_path.display())
|
||||||
|
);
|
||||||
|
|
||||||
|
let bindir = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("--prefix")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let wasmer_dir = Path::new(env!("WASMER_DIR"));
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(bindir.stdout).unwrap(),
|
||||||
|
format!("{}\n", wasmer_dir.display())
|
||||||
|
);
|
||||||
|
|
||||||
|
let bindir = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("--pkg-config")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let bin_path = format!("{}", bin_path.display());
|
||||||
|
let include_path = format!("{}", include_path.display());
|
||||||
|
let lib_path = format!("{}", lib_path.display());
|
||||||
|
let wasmer_dir = format!("{}", wasmer_dir.display());
|
||||||
|
|
||||||
|
let args = vec![
|
||||||
|
format!("prefix={wasmer_dir}"),
|
||||||
|
format!("exec_prefix={bin_path}"),
|
||||||
|
format!("includedir={include_path}"),
|
||||||
|
format!("libdir={lib_path}"),
|
||||||
|
format!(""),
|
||||||
|
format!("Name: wasmer"),
|
||||||
|
format!("Description: The Wasmer library for running WebAssembly"),
|
||||||
|
format!("Version: {}", env!("CARGO_PKG_VERSION")),
|
||||||
|
format!("Cflags: -I{include_path}"),
|
||||||
|
format!("Libs: -L{lib_path} -lwasmer"),
|
||||||
|
];
|
||||||
|
|
||||||
|
let lines = String::from_utf8(bindir.stdout)
|
||||||
|
.unwrap()
|
||||||
|
.lines()
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(lines, args);
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("--config-path")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let config_path = Path::new(env!("WASMER_DIR")).join("wasmer.toml");
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
format!("{}\n", config_path.display())
|
||||||
|
);
|
||||||
|
|
||||||
|
// ---- config get
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("get")
|
||||||
|
.arg("registry.token")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let original_token = String::from_utf8_lossy(&output.stdout);
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("set")
|
||||||
|
.arg("registry.token")
|
||||||
|
.arg("abc123")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
assert_eq!(String::from_utf8_lossy(&output.stdout), "".to_string());
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("get")
|
||||||
|
.arg("registry.token")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
"abc123\n".to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("set")
|
||||||
|
.arg("registry.token")
|
||||||
|
.arg(original_token.to_string().trim())
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
assert_eq!(String::from_utf8_lossy(&output.stdout), "".to_string());
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("get")
|
||||||
|
.arg("registry.token")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
format!("{}\n", original_token.to_string().trim().to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("get")
|
||||||
|
.arg("registry.url")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let original_url = String::from_utf8_lossy(&output.stdout);
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("set")
|
||||||
|
.arg("registry.url")
|
||||||
|
.arg("wapm.dev")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||||
|
|
||||||
|
assert_eq!(output_str, "".to_string());
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("get")
|
||||||
|
.arg("registry.url")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||||
|
assert_eq!(
|
||||||
|
output_str,
|
||||||
|
"https://registry.wapm.dev/graphql\n".to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("set")
|
||||||
|
.arg("registry.url")
|
||||||
|
.arg(original_url.to_string().trim())
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||||
|
assert_eq!(output_str, "".to_string());
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("get")
|
||||||
|
.arg("registry.url")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||||
|
assert_eq!(output_str, original_url.to_string());
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("get")
|
||||||
|
.arg("telemetry.enabled")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let original_output = String::from_utf8_lossy(&output.stdout);
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("set")
|
||||||
|
.arg("telemetry.enabled")
|
||||||
|
.arg("true")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
assert_eq!(String::from_utf8_lossy(&output.stdout), "".to_string());
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("get")
|
||||||
|
.arg("telemetry.enabled")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
"true\n".to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("set")
|
||||||
|
.arg("telemetry.enabled")
|
||||||
|
.arg(original_output.to_string().trim())
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
assert_eq!(String::from_utf8_lossy(&output.stdout), "".to_string());
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("get")
|
||||||
|
.arg("telemetry.enabled")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
original_output.to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("get")
|
||||||
|
.arg("update-notifications.enabled")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let original_output = String::from_utf8_lossy(&output.stdout);
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("set")
|
||||||
|
.arg("update-notifications.enabled")
|
||||||
|
.arg("true")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
assert_eq!(String::from_utf8_lossy(&output.stdout), "".to_string());
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("get")
|
||||||
|
.arg("update-notifications.enabled")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
"true\n".to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("set")
|
||||||
|
.arg("update-notifications.enabled")
|
||||||
|
.arg(original_output.to_string().trim())
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
assert_eq!(String::from_utf8_lossy(&output.stdout), "".to_string());
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("config")
|
||||||
|
.arg("get")
|
||||||
|
.arg("update-notifications.enabled")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8_lossy(&output.stdout),
|
||||||
|
original_output.to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -277,7 +277,7 @@ fn create_obj(args: Vec<&'static str>, keyword_needle: &str, keyword: &str) -> a
|
|||||||
let object_path = operating_dir.join("wasm.obj");
|
let object_path = operating_dir.join("wasm.obj");
|
||||||
|
|
||||||
let output: Vec<u8> = WasmerCreateObj {
|
let output: Vec<u8> = WasmerCreateObj {
|
||||||
current_dir: operating_dir.clone(),
|
current_dir: operating_dir,
|
||||||
wasm_path,
|
wasm_path,
|
||||||
output_object_path: object_path.clone(),
|
output_object_path: object_path.clone(),
|
||||||
compiler: Compiler::Cranelift,
|
compiler: Compiler::Cranelift,
|
||||||
@ -292,7 +292,7 @@ fn create_obj(args: Vec<&'static str>, keyword_needle: &str, keyword: &str) -> a
|
|||||||
"create-obj successfully completed but object output file `{}` missing",
|
"create-obj successfully completed but object output file `{}` missing",
|
||||||
object_path.display()
|
object_path.display()
|
||||||
);
|
);
|
||||||
let mut object_header_path = object_path.clone();
|
let mut object_header_path = object_path;
|
||||||
object_header_path.set_extension("h");
|
object_header_path.set_extension("h");
|
||||||
assert!(
|
assert!(
|
||||||
object_header_path.exists(),
|
object_header_path.exists(),
|
||||||
|
20
tests/integration/cli/tests/fixtures/init1.toml
vendored
Normal file
20
tests/integration/cli/tests/fixtures/init1.toml
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = 'ciuser/testfirstproject'
|
||||||
|
version = '0.1.0'
|
||||||
|
description = 'Description for package testfirstproject'
|
||||||
|
|
||||||
|
# See more keys and definitions at https://docs.wasmer.io/ecosystem/wapm/manifest
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
[[module]]
|
||||||
|
name = 'testfirstproject'
|
||||||
|
source = 'testfirstproject.wasm'
|
||||||
|
abi = 'wasi'
|
||||||
|
|
||||||
|
[module.interfaces]
|
||||||
|
wasi = '0.1.0-unstable'
|
||||||
|
|
||||||
|
[[command]]
|
||||||
|
name = 'testfirstproject'
|
||||||
|
module = 'testfirstproject'
|
6
tests/integration/cli/tests/fixtures/init2.toml
vendored
Normal file
6
tests/integration/cli/tests/fixtures/init2.toml
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "wasmer"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
20
tests/integration/cli/tests/fixtures/init4.toml
vendored
Normal file
20
tests/integration/cli/tests/fixtures/init4.toml
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = 'ciuser/wasmer'
|
||||||
|
version = '0.1.0'
|
||||||
|
description = 'Description for package wasmer'
|
||||||
|
|
||||||
|
# See more keys and definitions at https://docs.wasmer.io/ecosystem/wapm/manifest
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
[[module]]
|
||||||
|
name = 'wasmer'
|
||||||
|
source = 'target/wasm32-wasi/release/wasmer.wasm'
|
||||||
|
abi = 'wasi'
|
||||||
|
|
||||||
|
[module.interfaces]
|
||||||
|
wasi = '0.1.0-unstable'
|
||||||
|
|
||||||
|
[[command]]
|
||||||
|
name = 'wasmer'
|
||||||
|
module = 'wasmer'
|
13
tests/integration/cli/tests/fixtures/init6.toml
vendored
Normal file
13
tests/integration/cli/tests/fixtures/init6.toml
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "WAPMUSERNAME/largewasmfile"
|
||||||
|
version = "RANDOMVERSION1.RANDOMVERSION2.RANDOMVERSION3"
|
||||||
|
description = "published from wasmer largewasmfile"
|
||||||
|
|
||||||
|
[[module]]
|
||||||
|
name = "largewasmfile"
|
||||||
|
source = "largewasmfile.wasm"
|
||||||
|
abi = "wasi"
|
||||||
|
|
||||||
|
[[command]]
|
||||||
|
name = "largewasmfile"
|
||||||
|
module = "largewasmfile"
|
127
tests/integration/cli/tests/init.rs
Normal file
127
tests/integration/cli/tests/init.rs
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
use anyhow::bail;
|
||||||
|
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
use wasmer_integration_tests_cli::get_wasmer_path;
|
||||||
|
|
||||||
|
macro_rules! check_output {
|
||||||
|
($output:expr) => {
|
||||||
|
let stdout_output = std::str::from_utf8(&$output.stdout).unwrap();
|
||||||
|
let stderr_output = std::str::from_utf8(&$output.stdout).unwrap();
|
||||||
|
if !$output.status.success() {
|
||||||
|
bail!("wasmer init failed with: stdout: {stdout_output}\n\nstderr: {stderr_output}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that wasmer init without arguments works
|
||||||
|
#[test]
|
||||||
|
fn wasmer_init_works_1() -> anyhow::Result<()> {
|
||||||
|
let tempdir = tempfile::tempdir()?;
|
||||||
|
let path = tempdir.path();
|
||||||
|
let path = path.join("testfirstproject");
|
||||||
|
std::fs::create_dir_all(&path)?;
|
||||||
|
|
||||||
|
if std::env::var("GITHUB_TOKEN").is_err() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let wapm_dev_token = std::env::var("WAPM_DEV_TOKEN").ok();
|
||||||
|
println!("wapm dev token ok...");
|
||||||
|
|
||||||
|
if let Some(token) = wapm_dev_token {
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("login")
|
||||||
|
.arg("--registry")
|
||||||
|
.arg("wapm.dev")
|
||||||
|
.arg(token)
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.output()?;
|
||||||
|
check_output!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("wasmer login ok!");
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("init")
|
||||||
|
.current_dir(&path)
|
||||||
|
.output()?;
|
||||||
|
check_output!(output);
|
||||||
|
|
||||||
|
let read = std::fs::read_to_string(path.join("wasmer.toml"))
|
||||||
|
.unwrap()
|
||||||
|
.lines()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
let target = include_str!("./fixtures/init1.toml")
|
||||||
|
.lines()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
pretty_assertions::assert_eq!(read.trim(), target.trim());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wasmer_init_works_2() -> anyhow::Result<()> {
|
||||||
|
let tempdir = tempfile::tempdir()?;
|
||||||
|
let path = tempdir.path();
|
||||||
|
let path = path.join("testfirstproject");
|
||||||
|
std::fs::create_dir_all(&path)?;
|
||||||
|
std::fs::write(
|
||||||
|
path.join("Cargo.toml"),
|
||||||
|
include_bytes!("./fixtures/init2.toml"),
|
||||||
|
)?;
|
||||||
|
std::fs::create_dir_all(path.join("src"))?;
|
||||||
|
std::fs::write(path.join("src").join("main.rs"), b"fn main() { }")?;
|
||||||
|
|
||||||
|
if std::env::var("GITHUB_TOKEN").is_err() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let wapm_dev_token = std::env::var("WAPM_DEV_TOKEN").ok();
|
||||||
|
println!("wapm dev token ok...");
|
||||||
|
|
||||||
|
if let Some(token) = wapm_dev_token.as_ref() {
|
||||||
|
let mut cmd = Command::new(get_wasmer_path());
|
||||||
|
cmd.arg("login");
|
||||||
|
cmd.arg("--registry");
|
||||||
|
cmd.arg("wapm.dev");
|
||||||
|
cmd.arg(token);
|
||||||
|
cmd.stdout(Stdio::inherit());
|
||||||
|
cmd.stderr(Stdio::inherit());
|
||||||
|
cmd.stdin(Stdio::null());
|
||||||
|
let output = cmd.output()?;
|
||||||
|
check_output!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("wasmer login ok!");
|
||||||
|
|
||||||
|
let output = Command::new(get_wasmer_path())
|
||||||
|
.arg("init")
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.current_dir(&path)
|
||||||
|
.output()?;
|
||||||
|
check_output!(output);
|
||||||
|
|
||||||
|
pretty_assertions::assert_eq!(
|
||||||
|
std::fs::read_to_string(path.join("Cargo.toml")).unwrap(),
|
||||||
|
include_str!("./fixtures/init2.toml")
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("ok 1");
|
||||||
|
|
||||||
|
let read = std::fs::read_to_string(path.join("wasmer.toml"))
|
||||||
|
.unwrap()
|
||||||
|
.lines()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
let target = include_str!("./fixtures/init4.toml")
|
||||||
|
.lines()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
pretty_assertions::assert_eq!(read.trim(), target.trim());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use wasmer_integration_tests_cli::{get_repo_root_path, get_wasmer_path, ASSET_PATH, C_ASSET_PATH};
|
use wasmer_integration_tests_cli::get_wasmer_path;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn login_works() -> anyhow::Result<()> {
|
fn login_works() -> anyhow::Result<()> {
|
||||||
@ -11,6 +11,10 @@ fn login_works() -> anyhow::Result<()> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let wapm_dev_token = std::env::var("WAPM_DEV_TOKEN").expect("WAPM_DEV_TOKEN env var not set");
|
let wapm_dev_token = std::env::var("WAPM_DEV_TOKEN").expect("WAPM_DEV_TOKEN env var not set");
|
||||||
|
// Special case: GitHub secrets aren't visible to outside collaborators
|
||||||
|
if wapm_dev_token.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let output = Command::new(get_wasmer_path())
|
let output = Command::new(get_wasmer_path())
|
||||||
.arg("login")
|
.arg("login")
|
||||||
.arg("--registry")
|
.arg("--registry")
|
||||||
|
146
tests/integration/cli/tests/publish.rs
Normal file
146
tests/integration/cli/tests/publish.rs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
use std::process::Stdio;
|
||||||
|
use wasmer_integration_tests_cli::{get_wasmer_path, C_ASSET_PATH};
|
||||||
|
|
||||||
|
fn create_exe_test_wasm_path() -> String {
|
||||||
|
format!("{}/{}", C_ASSET_PATH, "qjs.wasm")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wasmer_publish() -> anyhow::Result<()> {
|
||||||
|
// Only run this test in the CI
|
||||||
|
if std::env::var("GITHUB_TOKEN").is_err() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let wapm_dev_token = std::env::var("WAPM_DEV_TOKEN").ok();
|
||||||
|
let tempdir = tempfile::tempdir()?;
|
||||||
|
let path = tempdir.path();
|
||||||
|
let username = "ciuser";
|
||||||
|
|
||||||
|
let random1 = format!("{}", rand::random::<u32>());
|
||||||
|
let random2 = format!("{}", rand::random::<u32>());
|
||||||
|
let random3 = format!("{}", rand::random::<u32>());
|
||||||
|
|
||||||
|
std::fs::copy(create_exe_test_wasm_path(), path.join("largewasmfile.wasm")).unwrap();
|
||||||
|
std::fs::write(
|
||||||
|
path.join("wasmer.toml"),
|
||||||
|
include_str!("./fixtures/init6.toml")
|
||||||
|
.replace("WAPMUSERNAME", username) // <-- TODO!
|
||||||
|
.replace("RANDOMVERSION1", &random1)
|
||||||
|
.replace("RANDOMVERSION2", &random2)
|
||||||
|
.replace("RANDOMVERSION3", &random3),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut cmd = std::process::Command::new(get_wasmer_path());
|
||||||
|
cmd.arg("publish");
|
||||||
|
cmd.arg("--quiet");
|
||||||
|
cmd.arg("--registry");
|
||||||
|
cmd.arg("wapm.dev");
|
||||||
|
cmd.arg(path);
|
||||||
|
|
||||||
|
if let Some(token) = wapm_dev_token {
|
||||||
|
cmd.arg("--token");
|
||||||
|
cmd.arg(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = cmd.stdin(Stdio::null()).output().unwrap();
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
|
||||||
|
assert_eq!(stdout, format!("Successfully published package `{username}/largewasmfile@{random1}.{random2}.{random3}`\n"), "failed to publish: {cmd:?}: {stderr}");
|
||||||
|
|
||||||
|
println!("wasmer publish ok! test done.");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs a full integration test to test that the flow wasmer init - cargo build -
|
||||||
|
// wasmer publish is working
|
||||||
|
#[test]
|
||||||
|
fn wasmer_init_publish() -> anyhow::Result<()> {
|
||||||
|
// Only run this test in the CI
|
||||||
|
if std::env::var("GITHUB_TOKEN").is_err() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let wapm_dev_token = std::env::var("WAPM_DEV_TOKEN").ok();
|
||||||
|
let tempdir = tempfile::tempdir()?;
|
||||||
|
let path = tempdir.path();
|
||||||
|
let username = "ciuser";
|
||||||
|
|
||||||
|
let random1 = format!("{}", rand::random::<u32>());
|
||||||
|
let random2 = format!("{}", rand::random::<u32>());
|
||||||
|
let random3 = format!("{}", rand::random::<u32>());
|
||||||
|
|
||||||
|
let mut cmd = std::process::Command::new("cargo");
|
||||||
|
cmd.arg("init");
|
||||||
|
cmd.arg("--bin");
|
||||||
|
cmd.arg(path.join("randomversion"));
|
||||||
|
|
||||||
|
let _ = cmd
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut cmd = std::process::Command::new("cargo");
|
||||||
|
cmd.arg("build");
|
||||||
|
cmd.arg("--release");
|
||||||
|
cmd.arg("--target");
|
||||||
|
cmd.arg("wasm32-wasi");
|
||||||
|
cmd.arg("--manifest-path");
|
||||||
|
cmd.arg(path.join("randomversion").join("Cargo.toml"));
|
||||||
|
|
||||||
|
let _ = cmd
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// generate the wasmer.toml
|
||||||
|
let mut cmd = std::process::Command::new(get_wasmer_path());
|
||||||
|
cmd.arg("init");
|
||||||
|
cmd.arg("--namespace");
|
||||||
|
cmd.arg(username);
|
||||||
|
cmd.arg("--version");
|
||||||
|
cmd.arg(format!("{random1}.{random2}.{random3}"));
|
||||||
|
cmd.arg(path.join("randomversion"));
|
||||||
|
|
||||||
|
let _ = cmd
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let s = std::fs::read_to_string(path.join("randomversion").join("wasmer.toml")).unwrap();
|
||||||
|
|
||||||
|
println!("{s}");
|
||||||
|
|
||||||
|
// publish
|
||||||
|
let mut cmd = std::process::Command::new(get_wasmer_path());
|
||||||
|
cmd.arg("publish");
|
||||||
|
cmd.arg("--quiet");
|
||||||
|
cmd.arg("--registry");
|
||||||
|
cmd.arg("wapm.dev");
|
||||||
|
cmd.arg(path.join("randomversion"));
|
||||||
|
|
||||||
|
if let Some(token) = wapm_dev_token {
|
||||||
|
cmd.arg("--token");
|
||||||
|
cmd.arg(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = cmd.stdin(Stdio::null()).output().unwrap();
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
|
||||||
|
assert_eq!(stdout, format!("Successfully published package `{username}/randomversion@{random1}.{random2}.{random3}`\n"), "failed to publish: {cmd:?}: {stderr}");
|
||||||
|
|
||||||
|
println!("wasmer init publish ok! test done.");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -112,6 +112,10 @@ fn run_whoami_works() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let ciuser_token = std::env::var("WAPM_DEV_TOKEN").expect("no CIUSER / WAPM_DEV_TOKEN token");
|
let ciuser_token = std::env::var("WAPM_DEV_TOKEN").expect("no CIUSER / WAPM_DEV_TOKEN token");
|
||||||
|
// Special case: GitHub secrets aren't visible to outside collaborators
|
||||||
|
if ciuser_token.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let output = Command::new(get_wasmer_path())
|
let output = Command::new(get_wasmer_path())
|
||||||
.arg("login")
|
.arg("login")
|
||||||
@ -352,12 +356,12 @@ fn test_wasmer_run_works_with_dir() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
std::fs::copy(wasi_test_wasm_path(), &qjs_path)?;
|
std::fs::copy(wasi_test_wasm_path(), &qjs_path)?;
|
||||||
std::fs::copy(
|
std::fs::copy(
|
||||||
format!("{}/{}", C_ASSET_PATH, "qjs-wapm.toml"),
|
format!("{}/{}", C_ASSET_PATH, "qjs-wasmer.toml"),
|
||||||
temp_dir.path().join("wapm.toml"),
|
temp_dir.path().join("wasmer.toml"),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
assert!(temp_dir.path().exists());
|
assert!(temp_dir.path().exists());
|
||||||
assert!(temp_dir.path().join("wapm.toml").exists());
|
assert!(temp_dir.path().join("wasmer.toml").exists());
|
||||||
assert!(temp_dir.path().join("qjs.wasm").exists());
|
assert!(temp_dir.path().join("qjs.wasm").exists());
|
||||||
|
|
||||||
// test with "wasmer qjs.wasm"
|
// test with "wasmer qjs.wasm"
|
||||||
|
@ -40,9 +40,8 @@ mod tests {
|
|||||||
*/
|
*/
|
||||||
let command_success = command.status.success();
|
let command_success = command.status.success();
|
||||||
let test_success = !stderr.contains("** TEST FAILED **");
|
let test_success = !stderr.contains("** TEST FAILED **");
|
||||||
let success = command_success && test_success;
|
|
||||||
|
|
||||||
success
|
command_success && test_success
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_existing_artificats() -> Output {
|
fn remove_existing_artificats() -> Output {
|
||||||
|
Reference in New Issue
Block a user