mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-07 05:08:19 +00:00
Merge branch 'master' into fast-ci
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,7 +12,6 @@ api-docs-repo/
|
||||
/src/windows-installer/WasmerInstaller.exe
|
||||
/lib/c-api/wasmer.h
|
||||
.xwin-cache
|
||||
wapm.toml
|
||||
# Generated by tests on Android
|
||||
/avd
|
||||
/core
|
||||
|
||||
331
Cargo.lock
generated
331
Cargo.lock
generated
@@ -73,12 +73,6 @@ version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.2"
|
||||
@@ -182,7 +176,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec 0.7.2",
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if 1.0.0",
|
||||
"constant_time_eq",
|
||||
@@ -270,38 +264,6 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c58ec36aac5066d5ca17df51b3e70279f5670a72102f5752cb7e7c856adfc70"
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-platform"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo_metadata"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "982a0cf6a99c350d7246035613882e376d58cebe571785abc5da4f648d53ac0a"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"cargo-platform",
|
||||
"semver 1.0.14",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
@@ -362,16 +324,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.34.0"
|
||||
@@ -1103,12 +1055,6 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-streaming-iterator"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.8.0"
|
||||
@@ -1485,15 +1431,6 @@ dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa"
|
||||
dependencies = [
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.3"
|
||||
@@ -1524,15 +1461,6 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.8"
|
||||
@@ -1684,18 +1612,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.17.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4295cbb7573c16d310e99e713cf9e75101eb190ab31fccd35f2d2691b4352b19"
|
||||
dependencies = [
|
||||
"console",
|
||||
"number_prefix",
|
||||
"portable-atomic",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inkwell"
|
||||
version = "0.1.0-beta.4"
|
||||
@@ -1750,15 +1666,6 @@ dependencies = [
|
||||
"rustc_version 0.3.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
@@ -1850,19 +1757,6 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "lexical-core"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
|
||||
dependencies = [
|
||||
"arrayvec 0.5.2",
|
||||
"bitflags",
|
||||
"cfg-if 1.0.0",
|
||||
"ryu",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lexical-sort"
|
||||
version = "0.3.1"
|
||||
@@ -1899,17 +1793,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.7"
|
||||
@@ -1919,6 +1802,12 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "llvm-sys"
|
||||
version = "120.2.5"
|
||||
@@ -2082,17 +1971,6 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "minisign"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce49953dd06a44e1034590bb619bfe8900c29500053c0c0f83e9260a34466aa5"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"rpassword",
|
||||
"scrypt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.5.4"
|
||||
@@ -2164,17 +2042,6 @@ dependencies = [
|
||||
"pin-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
|
||||
dependencies = [
|
||||
"lexical-core",
|
||||
"memchr",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.1"
|
||||
@@ -2226,12 +2093,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "number_prefix"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.28.4"
|
||||
@@ -2333,15 +2194,6 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
@@ -2404,12 +2256,6 @@ dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac662b3a6490de378b0ee15cf2dfff7127aebfe0b19acc65e7fbca3d299c3788"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
@@ -2865,41 +2711,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rpassword"
|
||||
version = "7.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rtoolbox",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rtoolbox"
|
||||
version = "0.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
"libsqlite3-sys",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.21"
|
||||
@@ -2991,15 +2802,6 @@ version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
||||
|
||||
[[package]]
|
||||
name = "salsa20"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
@@ -3027,18 +2829,6 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
|
||||
|
||||
[[package]]
|
||||
name = "scrypt"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
"pbkdf2",
|
||||
"salsa20",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.6.1"
|
||||
@@ -3207,15 +2997,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.14"
|
||||
version = "0.8.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d232d893b10de3eb7258ff01974d6ee20663d8e833263c99409d4b13a0209da"
|
||||
checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa 1.0.4",
|
||||
"ryu",
|
||||
"serde",
|
||||
"unsafe-libyaml",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3339,12 +3128,6 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "stdweb"
|
||||
version = "0.4.20"
|
||||
@@ -3608,29 +3391,11 @@ dependencies = [
|
||||
"libc",
|
||||
"standback",
|
||||
"stdweb",
|
||||
"time-macros 0.1.1",
|
||||
"time-macros",
|
||||
"version_check",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
|
||||
dependencies = [
|
||||
"itoa 1.0.4",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros 0.2.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.1.1"
|
||||
@@ -3641,15 +3406,6 @@ dependencies = [
|
||||
"time-macros-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros-impl"
|
||||
version = "0.1.2"
|
||||
@@ -3938,12 +3694,6 @@ dependencies = [
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
@@ -3962,12 +3712,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.1.1"
|
||||
@@ -4018,14 +3762,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wapm-toml"
|
||||
version = "0.4.0"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "994ef26447f3158955d2e3fca96021d1f1c47b830e2053569177673dca1447a9"
|
||||
checksum = "a61b6d3b6a2fc171198e6378b3a9b38650e114298775a9e63401613abb6a10b3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"semver 1.0.14",
|
||||
"serde",
|
||||
"serde_cbor",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"thiserror",
|
||||
@@ -4311,7 +4056,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"atty",
|
||||
"bytesize",
|
||||
"cargo_metadata",
|
||||
"cfg-if 1.0.0",
|
||||
"chrono",
|
||||
"clap 3.2.23",
|
||||
@@ -4320,29 +4064,21 @@ dependencies = [
|
||||
"dirs",
|
||||
"distance",
|
||||
"fern",
|
||||
"flate2",
|
||||
"hex",
|
||||
"http_req",
|
||||
"isatty",
|
||||
"libc",
|
||||
"log",
|
||||
"minisign",
|
||||
"nuke-dir",
|
||||
"prettytable-rs",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"rpassword",
|
||||
"rusqlite",
|
||||
"semver 1.0.14",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"spinoff",
|
||||
"tar",
|
||||
"target-lexicon 0.12.5",
|
||||
"tempdir",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"time 0.3.17",
|
||||
"tldextract",
|
||||
"toml",
|
||||
"unix_mode",
|
||||
@@ -4363,9 +4099,7 @@ dependencies = [
|
||||
"wasmer-vm",
|
||||
"wasmer-wasi",
|
||||
"wasmer-wasi-experimental-io-devices",
|
||||
"wasmer-wasm-interface",
|
||||
"wasmer-wast",
|
||||
"wasmparser 0.51.4",
|
||||
"webc",
|
||||
]
|
||||
|
||||
@@ -4498,7 +4232,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"time 0.2.27",
|
||||
"time",
|
||||
"wasmer",
|
||||
"wasmer-types",
|
||||
]
|
||||
@@ -4537,7 +4271,6 @@ version = "3.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"flate2",
|
||||
"pretty_assertions",
|
||||
"rand 0.8.5",
|
||||
"tar",
|
||||
"target-lexicon 0.12.5",
|
||||
@@ -4571,7 +4304,6 @@ name = "wasmer-registry"
|
||||
version = "3.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"console",
|
||||
"dirs",
|
||||
"filetime",
|
||||
"flate2",
|
||||
@@ -4579,8 +4311,6 @@ dependencies = [
|
||||
"futures-util",
|
||||
"graphql_client",
|
||||
"hex",
|
||||
"indicatif",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"lzma-rs",
|
||||
"rand 0.8.5",
|
||||
@@ -4733,7 +4463,7 @@ dependencies = [
|
||||
"byteorder",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"time 0.2.27",
|
||||
"time",
|
||||
"wasmer",
|
||||
"wasmer-derive",
|
||||
"wasmer-types",
|
||||
@@ -4743,18 +4473,6 @@ dependencies = [
|
||||
"wasmer-wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmer-wasm-interface"
|
||||
version = "3.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"either",
|
||||
"nom 5.1.2",
|
||||
"serde",
|
||||
"wasmparser 0.51.4",
|
||||
"wat",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmer-wast"
|
||||
version = "3.1.0"
|
||||
@@ -4868,12 +4586,6 @@ dependencies = [
|
||||
"wasmer-wast",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.51.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aeb1956b19469d1c5e63e459d29e7b5aa0f558d9f16fcef09736f8a265e6c10a"
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.83.0"
|
||||
@@ -5260,7 +4972,7 @@ version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7"
|
||||
dependencies = [
|
||||
"nom 7.1.1",
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5269,6 +4981,15 @@ version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "0.5.1"
|
||||
|
||||
@@ -47,7 +47,6 @@ members = [
|
||||
"lib/wasi-types",
|
||||
"lib/wasi-experimental-io-devices",
|
||||
"lib/wasi-local-networking",
|
||||
"lib/wasm-interface",
|
||||
"lib/c-api/tests/wasmer-c-api-test-runner",
|
||||
"lib/c-api/examples/wasmer-capi-examples-runner",
|
||||
"lib/types",
|
||||
|
||||
3
Makefile
3
Makefile
@@ -401,7 +401,7 @@ else
|
||||
strip --strip-unneeded target/$(HOST_TARGET)/release/wasmer-headless
|
||||
endif
|
||||
|
||||
WAPM_VERSION = v0.5.9
|
||||
WAPM_VERSION = v0.5.3
|
||||
get-wapm:
|
||||
[ -d "wapm-cli" ] || git clone --branch $(WAPM_VERSION) https://github.com/wasmerio/wapm-cli.git
|
||||
|
||||
@@ -570,7 +570,6 @@ test-wasi:
|
||||
$(CARGO_BINARY) test $(CARGO_TARGET) --release --tests $(compiler_features) -- wasi::wasitests
|
||||
|
||||
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
|
||||
|
||||
test-integration-cli-ci:
|
||||
|
||||
@@ -40,8 +40,6 @@ wasmer-types = { version = "=3.1.0", path = "../types" }
|
||||
wasmer-registry = { version = "=3.1.0", path = "../registry" }
|
||||
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-wasm-interface = { version = "3.1.0", path = "../wasm-interface" }
|
||||
wasmparser = "0.51.4"
|
||||
atty = "0.2"
|
||||
colored = "2.0"
|
||||
anyhow = "1.0"
|
||||
@@ -54,6 +52,7 @@ bytesize = "1.0"
|
||||
cfg-if = "1.0"
|
||||
# For debug feature
|
||||
fern = { version = "0.6", features = ["colored"], optional = true }
|
||||
log = { version = "0.4", optional = true }
|
||||
tempfile = "3"
|
||||
tempdir = "0.3.7"
|
||||
http_req = { version="^0.8", default-features = false, features = ["rust-tls"] }
|
||||
@@ -63,7 +62,7 @@ dirs = { version = "4.0" }
|
||||
serde_json = { version = "1.0" }
|
||||
target-lexicon = { version = "0.12", features = ["std"] }
|
||||
prettytable-rs = "0.9.0"
|
||||
wapm-toml = "0.4.0"
|
||||
wapm-toml = "0.2.0"
|
||||
walkdir = "2.3.2"
|
||||
regex = "1.6.0"
|
||||
toml = "0.5.9"
|
||||
@@ -75,16 +74,6 @@ isatty = "0.1.9"
|
||||
dialoguer = "0.10.2"
|
||||
tldextract = "0.6.0"
|
||||
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]
|
||||
chrono = { version = "^0.4", default-features = false, features = [ "std", "clock" ] }
|
||||
@@ -155,7 +144,7 @@ llvm = [
|
||||
"wasmer-compiler-llvm",
|
||||
"compiler",
|
||||
]
|
||||
debug = ["fern", "wasmer-wasi/logging"]
|
||||
debug = ["fern", "log", "wasmer-wasi/logging"]
|
||||
disable-all-logging = ["wasmer-wasi/disable-all-logging"]
|
||||
headless = []
|
||||
headless-minimal = ["headless", "disable-all-logging", "wasi"]
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
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)
|
||||
);
|
||||
@@ -1,9 +0,0 @@
|
||||
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)
|
||||
);
|
||||
@@ -1,26 +0,0 @@
|
||||
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")]
|
||||
use crate::commands::Wast;
|
||||
use crate::commands::{
|
||||
Add, Cache, Config, Init, Inspect, List, Login, Publish, Run, SelfUpdate, Validate, Whoami,
|
||||
Add, Cache, Config, Inspect, List, Login, Run, SelfUpdate, Validate, Whoami,
|
||||
};
|
||||
use crate::error::PrettyError;
|
||||
use clap::{CommandFactory, ErrorKind, Parser};
|
||||
@@ -46,10 +46,6 @@ enum WasmerCLIOptions {
|
||||
/// Login into a wapm.io-like registry
|
||||
Login(Login),
|
||||
|
||||
/// Login into a wapm.io-like registry
|
||||
#[clap(name = "publish")]
|
||||
Publish(Publish),
|
||||
|
||||
/// Wasmer cache
|
||||
#[clap(subcommand)]
|
||||
Cache(Cache),
|
||||
@@ -139,10 +135,6 @@ enum WasmerCLIOptions {
|
||||
/// Inspect a WebAssembly file
|
||||
Inspect(Inspect),
|
||||
|
||||
/// Initializes a new wapm.toml file
|
||||
#[clap(name = "init")]
|
||||
Init(Init),
|
||||
|
||||
/// Run spec testsuite
|
||||
#[cfg(feature = "wast")]
|
||||
Wast(Wast),
|
||||
@@ -173,10 +165,8 @@ impl WasmerCLIOptions {
|
||||
Self::CreateObj(create_obj) => create_obj.execute(),
|
||||
Self::Config(config) => config.execute(),
|
||||
Self::Inspect(inspect) => inspect.execute(),
|
||||
Self::Init(init) => init.execute(),
|
||||
Self::List(list) => list.execute(),
|
||||
Self::Login(login) => login.execute(),
|
||||
Self::Publish(publish) => publish.execute(),
|
||||
#[cfg(feature = "wast")]
|
||||
Self::Wast(wast) => wast.execute(),
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -234,9 +224,10 @@ fn wasmer_main_inner() -> Result<(), anyhow::Error> {
|
||||
WasmerCLIOptions::Run(Run::from_binfmt_args())
|
||||
} else {
|
||||
match command.unwrap_or(&"".to_string()).as_ref() {
|
||||
"add" | "cache" | "compile" | "config" | "create-exe" | "help" | "inspect" | "init"
|
||||
| "run" | "self-update" | "validate" | "wast" | "binfmt" | "list" | "login"
|
||||
| "publish" => WasmerCLIOptions::parse(),
|
||||
"add" | "cache" | "compile" | "config" | "create-exe" | "help" | "inspect" | "run"
|
||||
| "self-update" | "validate" | "wast" | "binfmt" | "list" | "login" => {
|
||||
WasmerCLIOptions::parse()
|
||||
}
|
||||
_ => {
|
||||
WasmerCLIOptions::try_parse_from(args.iter()).unwrap_or_else(|e| {
|
||||
match e.kind() {
|
||||
|
||||
@@ -10,11 +10,9 @@ mod config;
|
||||
mod create_exe;
|
||||
#[cfg(feature = "static-artifact-create")]
|
||||
mod create_obj;
|
||||
mod init;
|
||||
mod inspect;
|
||||
mod list;
|
||||
mod login;
|
||||
mod publish;
|
||||
mod run;
|
||||
mod self_update;
|
||||
mod validate;
|
||||
@@ -33,8 +31,8 @@ pub use create_obj::*;
|
||||
#[cfg(feature = "wast")]
|
||||
pub use wast::*;
|
||||
pub use {
|
||||
add::*, cache::*, config::*, init::*, inspect::*, list::*, login::*, publish::*, run::*,
|
||||
self_update::*, validate::*, whoami::*,
|
||||
add::*, cache::*, config::*, inspect::*, list::*, login::*, run::*, self_update::*,
|
||||
validate::*, whoami::*,
|
||||
};
|
||||
|
||||
/// The kind of object format to emit.
|
||||
|
||||
@@ -1,503 +0,0 @@
|
||||
use anyhow::Context;
|
||||
use cargo_metadata::{CargoOpt, MetadataCommand};
|
||||
use clap::Parser;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
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 wapm.toml for a library package
|
||||
#[clap(long, group = "crate-type")]
|
||||
pub lib: bool,
|
||||
/// Initialize wapm.toml for a binary package
|
||||
#[clap(long, group = "crate-type")]
|
||||
pub bin: bool,
|
||||
/// Initialize an empty wapm.toml
|
||||
#[clap(long, group = "crate-type")]
|
||||
pub empty: bool,
|
||||
/// Force overwriting the wapm.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 wapm.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 {
|
||||
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() {
|
||||
Some(parse_cargo_toml(&manifest_path)?)
|
||||
} 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: &wapm_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) => {
|
||||
let _ = std::fs::create_dir_all(s);
|
||||
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 = Path::new(&format!("/{path}")).to_path_buf();
|
||||
|
||||
(key, value)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_command(
|
||||
modules: &[wapm_toml::Module],
|
||||
bin_or_lib: BinOrLib,
|
||||
) -> Option<Vec<wapm_toml::Command>> {
|
||||
match bin_or_lib {
|
||||
BinOrLib::Bin => Some(
|
||||
modules
|
||||
.iter()
|
||||
.map(|m| {
|
||||
wapm_toml::Command::V1(wapm_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 wapm.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, true, _) | (true, _, true) => Err(anyhow::anyhow!(
|
||||
"cannot combine --empty with --bin or --lib"
|
||||
)),
|
||||
(true, false, false) => Ok(BinOrLib::Empty),
|
||||
(_, true, true) => Err(anyhow::anyhow!(
|
||||
"cannot initialize a wapm manifest with both --bin and --lib, pick one"
|
||||
)),
|
||||
(false, true, _) => Ok(BinOrLib::Bin),
|
||||
(false, _, true) => Ok(BinOrLib::Lib),
|
||||
_ => Ok(BinOrLib::Bin),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(wapm_toml::Bindings::Wit(wapm_toml::WitBindings {
|
||||
wit_exports: e.path().to_path_buf(),
|
||||
wit_bindgen: semver::Version::parse("0.1.0").unwrap(),
|
||||
}))
|
||||
} else if is_wai {
|
||||
Some(wapm_toml::Bindings::Wai(wapm_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(wapm_toml::Bindings),
|
||||
MultiBindings(Vec<wapm_toml::Bindings>),
|
||||
}
|
||||
|
||||
impl GetBindingsResult {
|
||||
fn first_binding(&self) -> Option<wapm_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,
|
||||
) -> wapm_toml::Manifest {
|
||||
let package_name = package_name.unwrap_or_else(|| {
|
||||
cargo_toml
|
||||
.as_ref()
|
||||
.map(|p| &p.name)
|
||||
.unwrap_or(fallback_package_name)
|
||||
});
|
||||
let namespace = namespace.or_else(|| wasmer_registry::whoami(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 = wapm_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 {
|
||||
wapm_toml::Bindings::Wit(wb) => {
|
||||
format!("found: {}", serde_json::to_string(wb).unwrap_or_default())
|
||||
}
|
||||
wapm_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![wapm_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
|
||||
}),
|
||||
}];
|
||||
|
||||
wapm_toml::Manifest {
|
||||
package: wapm_toml::Package {
|
||||
name: if wasmer_registry::Package::validate_package_name(package_name) {
|
||||
package_name.to_string()
|
||||
} else 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();
|
||||
|
||||
let metadata = match metadata {
|
||||
Ok(o) => o,
|
||||
Err(e) => {
|
||||
return Err(anyhow::anyhow!("failed to load metadata: {e}")
|
||||
.context(anyhow::anyhow!("{}", 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 {
|
||||
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,679 +0,0 @@
|
||||
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::Package;
|
||||
use wasmer_registry::PartialWapmConfig;
|
||||
|
||||
const CURRENT_DATA_VERSION: i32 = 3;
|
||||
|
||||
/// 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 = wapm_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();
|
||||
}
|
||||
|
||||
// See if a user is logged in. The backend should check for authorization on uploading
|
||||
let (registry, username) =
|
||||
wasmer_registry::whoami(self.registry.as_deref(), self.token.as_deref()).with_context(
|
||||
|| {
|
||||
anyhow::anyhow!(
|
||||
"could not find username / registry for registry = {:?}, token = {}",
|
||||
self.registry,
|
||||
self.token.as_deref().unwrap_or_default()
|
||||
)
|
||||
},
|
||||
)?;
|
||||
|
||||
let registry_present =
|
||||
wasmer_registry::test_if_registry_present(®istry).unwrap_or(false);
|
||||
|
||||
if !registry_present {
|
||||
return Err(anyhow::anyhow!(
|
||||
"registry {} is currently unavailable",
|
||||
registry
|
||||
));
|
||||
}
|
||||
|
||||
// If the package name is not a "/", try to prefix it with the current logged in user
|
||||
if !Package::validate_package_name(&manifest.package.name) {
|
||||
manifest.package.name = format!("{username}/{}", manifest.package.name);
|
||||
}
|
||||
|
||||
// Validate the name again
|
||||
if !Package::validate_package_name(&manifest.package.name) {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid package name {:?}",
|
||||
manifest.package.name
|
||||
));
|
||||
}
|
||||
|
||||
if !self.no_validate {
|
||||
validate::validate_directory(&manifest, ®istry, cwd.clone())?;
|
||||
}
|
||||
|
||||
builder.append_path_with_name(&manifest_path_buf, "wapm.toml")?;
|
||||
|
||||
let manifest_string = toml::to_string(&manifest)?;
|
||||
|
||||
let (readme, license) = construct_tar_gz(&mut builder, &manifest, &cwd)?;
|
||||
|
||||
builder.finish().ok();
|
||||
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(
|
||||
self.registry.clone(),
|
||||
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: &wapm_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"));
|
||||
};
|
||||
Ok(SignArchiveResult::Ok {
|
||||
public_key_id: personal_key.public_key_id,
|
||||
signature: (minisign::sign(
|
||||
Some(&minisign::PublicKey::from_base64(
|
||||
&personal_key.public_key_value,
|
||||
)?),
|
||||
&private_key,
|
||||
compressed_archive,
|
||||
None,
|
||||
None,
|
||||
)?
|
||||
.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 db_path =
|
||||
PartialWapmConfig::get_database_file_path().map_err(|e| anyhow::anyhow!("{e}"))?;
|
||||
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)?;
|
||||
}
|
||||
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 migrations = &[
|
||||
(0, include_str!("../../sql/migrations/0000.sql")),
|
||||
(1, include_str!("../../sql/migrations/0001.sql")),
|
||||
(2, include_str!("../../sql/migrations/0002.sql")),
|
||||
];
|
||||
|
||||
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)
|
||||
})?;
|
||||
|
||||
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: &wapm_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: &wapm_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] wapm_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());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
SELECT content
|
||||
FROM wasm_interfaces
|
||||
WHERE interface_name = (?1)
|
||||
AND version = (?2)
|
||||
@@ -1,3 +0,0 @@
|
||||
INSERT INTO wasm_interfaces
|
||||
(interface_name, version, date_added, content)
|
||||
VALUES (?1, ?2, ?3, ?4)
|
||||
@@ -1,5 +0,0 @@
|
||||
SELECT 1
|
||||
FROM wasm_interfaces
|
||||
WHERE interface_name = (?1)
|
||||
AND version = (?2)
|
||||
LIMIT 1
|
||||
@@ -11,7 +11,7 @@ pub struct Whoami {
|
||||
impl Whoami {
|
||||
/// Execute `wasmer whoami`
|
||||
pub fn execute(&self) -> Result<(), anyhow::Error> {
|
||||
let (registry, username) = wasmer_registry::whoami(self.registry.as_deref(), None)?;
|
||||
let (registry, username) = wasmer_registry::whoami(self.registry.as_deref())?;
|
||||
println!("logged into registry {registry:?} as user {username:?}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ serde_json = "1.0.85"
|
||||
url = "2.3.1"
|
||||
thiserror = "1.0.37"
|
||||
toml = "0.5.9"
|
||||
wapm-toml = "0.4.0"
|
||||
wapm-toml = "0.2.0"
|
||||
tar = "0.4.38"
|
||||
flate2 = "1.0.24"
|
||||
semver = "1.0.14"
|
||||
@@ -34,6 +34,3 @@ regex = "1.7.0"
|
||||
fs_extra = "1.2.0"
|
||||
filetime = "0.2.19"
|
||||
tldextract = "0.6.0"
|
||||
console = "0.15.2"
|
||||
indicatif = "0.17.2"
|
||||
lazy_static = "1.4.0"
|
||||
@@ -1,9 +0,0 @@
|
||||
query GetInterfaceVersionQuery ($name: String!, $version: String!) {
|
||||
interface: getInterfaceVersion(name: $name, version: $version) {
|
||||
version,
|
||||
content,
|
||||
interface {
|
||||
name,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
query GetSignedUrl ($name:String!, $version:String!,$expiresAfterSeconds:Int) {
|
||||
url: getSignedUrlForPackageUpload(name:$name, version:$version,expiresAfterSeconds:$expiresAfterSeconds) {
|
||||
url
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,6 @@ use serde::Serialize;
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub static GLOBAL_CONFIG_DATABASE_FILE_NAME: &str = "wapm.sqlite";
|
||||
|
||||
#[derive(Deserialize, Default, Serialize, Debug, PartialEq, Eq)]
|
||||
pub struct PartialWapmConfig {
|
||||
/// The number of seconds to wait before checking the registry for a new
|
||||
@@ -305,15 +303,6 @@ impl PartialWapmConfig {
|
||||
pub fn get_file_location() -> Result<PathBuf, String> {
|
||||
Ok(Self::get_folder()?.join(crate::GLOBAL_CONFIG_FILE_NAME))
|
||||
}
|
||||
|
||||
pub fn get_database_file_path(#[cfg(test)] test_name: &str) -> Result<PathBuf, String> {
|
||||
#[cfg(test)]
|
||||
let f = Self::get_folder(test_name);
|
||||
#[cfg(not(test))]
|
||||
let f = Self::get_folder();
|
||||
|
||||
f.map(|config_folder| config_folder.join(GLOBAL_CONFIG_DATABASE_FILE_NAME))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(GraphQLQuery)]
|
||||
|
||||
@@ -99,60 +99,6 @@ 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 {
|
||||
whoami::distro().to_lowercase()
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
#![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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,8 @@ use url::Url;
|
||||
|
||||
pub mod config;
|
||||
pub mod graphql;
|
||||
pub mod interface;
|
||||
pub mod login;
|
||||
pub mod package;
|
||||
pub mod publish;
|
||||
pub mod queries;
|
||||
pub mod utils;
|
||||
|
||||
@@ -96,7 +94,7 @@ impl LocalPackage {
|
||||
let path = self.get_path()?;
|
||||
#[cfg(test)]
|
||||
let path = self.get_path(test_name)?;
|
||||
let toml_path = path.join(GLOBAL_CONFIG_FILE_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)
|
||||
@@ -115,9 +113,8 @@ pub fn get_executable_file_from_path(
|
||||
package_dir: &PathBuf,
|
||||
command: Option<&str>,
|
||||
) -> Result<(wapm_toml::Manifest, PathBuf), anyhow::Error> {
|
||||
let wapm_toml = std::fs::read_to_string(package_dir.join(GLOBAL_CONFIG_FILE_NAME))
|
||||
.or_else(|_| std::fs::read_to_string(package_dir.join("wasmer.toml")))
|
||||
.map_err(|_| anyhow::anyhow!("Package {package_dir:?} has no {GLOBAL_CONFIG_FILE_NAME}"))?;
|
||||
let wapm_toml = std::fs::read_to_string(package_dir.join("wapm.toml"))
|
||||
.map_err(|_| anyhow::anyhow!("Package {package_dir:?} has no wapm.toml"))?;
|
||||
|
||||
let wapm_toml = toml::from_str::<wapm_toml::Manifest>(&wapm_toml)
|
||||
.map_err(|e| anyhow::anyhow!("Could not parse toml for {package_dir:?}: {e}"))?;
|
||||
@@ -153,7 +150,7 @@ pub fn get_executable_file_from_path(
|
||||
.find(|m| m.name == module_name)
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Cannot run {name}@{version}: module {module_name} not found in {GLOBAL_CONFIG_FILE_NAME}"
|
||||
"Cannot run {name}@{version}: module {module_name} not found in wapm.toml"
|
||||
)
|
||||
})?;
|
||||
|
||||
@@ -605,7 +602,6 @@ pub fn install_package(#[cfg(test)] test_name: &str, url: &Url) -> Result<PathBu
|
||||
pub fn whoami(
|
||||
#[cfg(test)] test_name: &str,
|
||||
registry: Option<&str>,
|
||||
token: Option<&str>,
|
||||
) -> Result<(String, String), anyhow::Error> {
|
||||
use crate::queries::{who_am_i_query, WhoAmIQuery};
|
||||
use graphql_client::GraphQLQuery;
|
||||
@@ -624,9 +620,9 @@ pub fn whoami(
|
||||
None => config.registry.get_current_registry(),
|
||||
};
|
||||
|
||||
let login_token = token
|
||||
.map(|s| s.to_string())
|
||||
.or_else(|| config.registry.get_login_token_for_registry(®istry))
|
||||
let login_token = config
|
||||
.registry
|
||||
.get_login_token_for_registry(®istry)
|
||||
.ok_or_else(|| anyhow::anyhow!("not logged into registry {:?}", registry))?;
|
||||
|
||||
let q = WhoAmIQuery::build_query(who_am_i_query::Variables {});
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
use crate::PartialWapmConfig;
|
||||
use regex::Regex;
|
||||
use std::path::PathBuf;
|
||||
use std::{fmt, str::FromStr};
|
||||
use url::Url;
|
||||
|
||||
const REGEX_FULL: &str = r#"^([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_]+)(@([a-zA-Z0-9\.\-_]+*))?$"#;
|
||||
const REGEX_PACKAGE: &str = r#"^([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_]+)$"#;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref FULL_REGEX: Regex = regex::Regex::new(REGEX_FULL).unwrap();
|
||||
static ref PACKAGE_REGEX: Regex = regex::Regex::new(REGEX_PACKAGE).unwrap();
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Package {
|
||||
pub namespace: String,
|
||||
@@ -175,17 +166,17 @@ impl Package {
|
||||
None => Ok(checkouts_dir.join(&hash)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate_package_name(s: &str) -> bool {
|
||||
PACKAGE_REGEX.is_match(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Package {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let captures = FULL_REGEX
|
||||
let regex =
|
||||
regex::Regex::new(r#"^([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_]+)(@([a-zA-Z0-9\.\-_]+*))?$"#)
|
||||
.unwrap();
|
||||
|
||||
let captures = regex
|
||||
.captures(s.trim())
|
||||
.map(|c| {
|
||||
c.iter()
|
||||
|
||||
@@ -1,243 +0,0 @@
|
||||
use crate::graphql::{execute_query_modifier_inner, get_signed_url, GetSignedUrl};
|
||||
use crate::graphql::{publish_package_mutation_chunked, PublishPackageMutationChunked};
|
||||
use crate::{format_graphql, PartialWapmConfig};
|
||||
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: &wapm_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 => {
|
||||
#[cfg(not(test))]
|
||||
let config = PartialWapmConfig::from_file();
|
||||
#[cfg(test)]
|
||||
let config = PartialWapmConfig::from_file("publish");
|
||||
|
||||
config
|
||||
.map_err(|e| anyhow::anyhow!("{e}"))?
|
||||
.registry
|
||||
.get_current_registry()
|
||||
}
|
||||
};
|
||||
|
||||
let token = match token.as_ref() {
|
||||
Some(s) => s.to_string(),
|
||||
None => {
|
||||
#[cfg(not(test))]
|
||||
let config = PartialWapmConfig::from_file();
|
||||
#[cfg(test)]
|
||||
let config = PartialWapmConfig::from_file("publish");
|
||||
|
||||
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,24 +0,0 @@
|
||||
[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"]
|
||||
@@ -1,88 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,205 +0,0 @@
|
||||
//! 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());
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#![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::*;
|
||||
@@ -1,596 +0,0 @@
|
||||
//! 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());
|
||||
}
|
||||
}
|
||||
@@ -1,465 +0,0 @@
|
||||
//! 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 },
|
||||
}
|
||||
@@ -12,7 +12,6 @@ rand = "0.8.5"
|
||||
tar = "0.4.38"
|
||||
flate2 = "1.0.24"
|
||||
target-lexicon = "0.12.4"
|
||||
pretty_assertions = "1.3.0"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
|
||||
20
tests/integration/cli/tests/fixtures/init1.toml
vendored
20
tests/integration/cli/tests/fixtures/init1.toml
vendored
@@ -1,20 +0,0 @@
|
||||
[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'
|
||||
@@ -1,6 +0,0 @@
|
||||
[package]
|
||||
name = "wasmer"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@@ -1,8 +0,0 @@
|
||||
[package]
|
||||
name = "wasmer"
|
||||
version = "0.5.6"
|
||||
description = "hello"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[package.metadata.wapm]
|
||||
20
tests/integration/cli/tests/fixtures/init4.toml
vendored
20
tests/integration/cli/tests/fixtures/init4.toml
vendored
@@ -1,20 +0,0 @@
|
||||
[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'
|
||||
@@ -1,7 +0,0 @@
|
||||
[package]
|
||||
name = "test-wasmer-init"
|
||||
version = "RANDOMVERSION1.RANDOMVERSION2.RANDOMVERSION3"
|
||||
edition = "2021"
|
||||
description = "description of package test-wasmer-init"
|
||||
|
||||
[dependencies]
|
||||
13
tests/integration/cli/tests/fixtures/init6.toml
vendored
13
tests/integration/cli/tests/fixtures/init6.toml
vendored
@@ -1,13 +0,0 @@
|
||||
[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"
|
||||
@@ -1,127 +0,0 @@
|
||||
use anyhow::bail;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
use wasmer_integration_tests_cli::{get_repo_root_path, get_wasmer_path, ASSET_PATH, C_ASSET_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,148 +0,0 @@
|
||||
use anyhow::bail;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
use wasmer_integration_tests_cli::{get_repo_root_path, get_wasmer_path, ASSET_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(())
|
||||
}
|
||||
@@ -352,12 +352,12 @@ fn test_wasmer_run_works_with_dir() -> anyhow::Result<()> {
|
||||
|
||||
std::fs::copy(wasi_test_wasm_path(), &qjs_path)?;
|
||||
std::fs::copy(
|
||||
format!("{}/{}", C_ASSET_PATH, "qjs-wasmer.toml"),
|
||||
temp_dir.path().join("wasmer.toml"),
|
||||
format!("{}/{}", C_ASSET_PATH, "qjs-wapm.toml"),
|
||||
temp_dir.path().join("wapm.toml"),
|
||||
)?;
|
||||
|
||||
assert!(temp_dir.path().exists());
|
||||
assert!(temp_dir.path().join("wasmer.toml").exists());
|
||||
assert!(temp_dir.path().join("wapm.toml").exists());
|
||||
assert!(temp_dir.path().join("qjs.wasm").exists());
|
||||
|
||||
// test with "wasmer qjs.wasm"
|
||||
|
||||
Reference in New Issue
Block a user