diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml index 9c449710631..8ca7b6dde36 100644 --- a/.github/workflows/cargo.yml +++ b/.github/workflows/cargo.yml @@ -115,7 +115,7 @@ jobs: - crate: swc os: ubuntu-latest check: | - cargo hack check --feature-powerset --no-dev-deps --exclude-features debug --exclude-features plugin + cargo hack check --feature-powerset --no-dev-deps --exclude-features debug --exclude-features plugin --exclude-features plugin-transform-schema-v1 - crate: swc os: windows-latest - crate: swc_atoms @@ -136,7 +136,7 @@ jobs: - crate: swc_common os: ubuntu-latest check: | - cargo hack check --feature-powerset --no-dev-deps + cargo hack check --feature-powerset --no-dev-deps --exclude-features plugin-transform-schema-vtest - crate: swc_common os: windows-latest - crate: swc_config @@ -398,9 +398,15 @@ jobs: swc-exec-cache-${{ matrix.settings.crate }}-${{ runner.os }} - name: Run cargo test + if: matrix.settings.crate != 'swc_plugin_runner' run: | cargo test --color always -p ${{ matrix.settings.crate }} + - name: Run cargo test (plugin) + if: matrix.settings.crate == 'swc_plugin_runner' + run: | + cargo test --color always -p swc_plugin_runner --features plugin-transform-schema-v1 + - name: Run cargo test (all features) if: matrix.settings.crate == 'swc_ecma_parser' || matrix.settings.crate == 'swc_ecma_loader' || matrix.settings.crate == 'swc_ecma_transforms' run: | diff --git a/crates/binding_core_node/Cargo.toml b/crates/binding_core_node/Cargo.toml index 8bc89402fbd..fcaa556cc64 100644 --- a/crates/binding_core_node/Cargo.toml +++ b/crates/binding_core_node/Cargo.toml @@ -16,7 +16,10 @@ crate-type = ["cdylib"] default = ["swc_v1", "plugin"] plugin = [ "swc/plugin", + "swc/plugin-transform-schema-v1", + "swc_common/plugin-transform-schema-v1", "swc_plugin_runner/default", + "swc_plugin_runner/plugin-transform-schema-v1", "wasmer/default", "wasmer-wasi/default", ] diff --git a/crates/binding_core_wasm/Cargo.toml b/crates/binding_core_wasm/Cargo.toml index 539de8b4463..887fa780fc0 100644 --- a/crates/binding_core_wasm/Cargo.toml +++ b/crates/binding_core_wasm/Cargo.toml @@ -20,6 +20,7 @@ swc_v2 = [] plugin = [ "swc/plugin", "swc_plugin_runner/memory_cache", + "swc_plugin_runner/plugin-transform-schema-v1", "wasmer", "wasmer-wasi", "wasmer/js-default", diff --git a/crates/swc/Cargo.toml b/crates/swc/Cargo.toml index 48c6c90392f..16fa47c8bb6 100644 --- a/crates/swc/Cargo.toml +++ b/crates/swc/Cargo.toml @@ -22,6 +22,10 @@ default = ["es3"] es3 = [] node = ["napi", "napi-derive"] plugin = ["swc_plugin_runner", "swc_plugin_proxy/plugin-rt"] +plugin-transform-schema-v1 = [ + "swc_common/plugin-transform-schema-v1", + "swc_plugin_runner/plugin-transform-schema-v1" +] [dependencies] ahash = "0.7.4" diff --git a/crates/swc/src/plugin.rs b/crates/swc/src/plugin.rs index eef298f26aa..0ac52ae0f26 100644 --- a/crates/swc/src/plugin.rs +++ b/crates/swc/src/plugin.rs @@ -127,6 +127,10 @@ impl RustPlugins { &self.source_map, )?; + if !transform_plugin_executor.is_transform_schema_compatible()? { + anyhow::bail!("Cannot execute incompatible plugin {}", &p.0); + } + let span = tracing::span!( tracing::Level::INFO, "serialize_context", diff --git a/crates/swc_cli/Cargo.toml b/crates/swc_cli/Cargo.toml index 1b5c424d3be..27f13f1a0eb 100644 --- a/crates/swc_cli/Cargo.toml +++ b/crates/swc_cli/Cargo.toml @@ -18,6 +18,7 @@ default = [] plugin = [ "swc/plugin", "swc_plugin_runner/filesystem_cache", + "swc_plugin_runner/plugin-transform-schema-v1", "wasmer/default", "wasmer-wasi/default", ] diff --git a/crates/swc_common/Cargo.toml b/crates/swc_common/Cargo.toml index 152bc75f2c3..29fc29fab5d 100644 --- a/crates/swc_common/Cargo.toml +++ b/crates/swc_common/Cargo.toml @@ -26,6 +26,8 @@ plugin-mode = ["plugin-base"] plugin-rt = ["plugin-base"] rkyv-impl = ["rkyv", "bytecheck"] tty-emitter = ["atty", "termcolor"] +plugin-transform-schema-v1 = [] +plugin-transform-schema-vtest = [] [dependencies] ahash = "0.7.4" diff --git a/crates/swc_common/src/plugin.rs b/crates/swc_common/src/plugin.rs index edc978abc4b..ff8720c98e3 100644 --- a/crates/swc_common/src/plugin.rs +++ b/crates/swc_common/src/plugin.rs @@ -12,6 +12,28 @@ use rkyv::{with::AsBox, Archive, Deserialize, Serialize}; use crate::{syntax_pos::Mark, SyntaxContext}; +/** + * Compile-time version constant for the AST struct schema's version. + * + * NOTE: this is for PARTIAL compatibility only, supporting if AST struct + * adds new properties without changing / removing existing properties. + * + * - When adding a new properties to the AST struct: + * 1. Create a new feature flag in cargo.toml + * 2. Create a new schema version with new feature flag. + * 3. Create a new AST struct with compile time feature flag with newly + * added properties. Previous struct should remain with existing feature + * flag, or add previous latest feature flag. + * + * - When removing, or changing existing properties in the AST struct: TBD + */ +#[cfg(feature = "plugin-transform-schema-v1")] +pub const PLUGIN_TRANSFORM_AST_SCHEMA_VERSION: u32 = 1; + +// Reserved for the testing purpose. +#[cfg(feature = "plugin-transform-schema-vtest")] +pub const PLUGIN_TRANSFORM_AST_SCHEMA_VERSION: u32 = u32::MAX - 1; + #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] #[cfg_attr( diff --git a/crates/swc_plugin/Cargo.toml b/crates/swc_plugin/Cargo.toml index e6fb561e38d..1e00454ffe0 100644 --- a/crates/swc_plugin/Cargo.toml +++ b/crates/swc_plugin/Cargo.toml @@ -22,6 +22,7 @@ quote = ["swc_ecma_quote"] swc_atoms = { version = "0.2.0", path = "../swc_atoms" } swc_common = { version = "0.23.0", path = "../swc_common", features = [ "plugin-mode", + "plugin-transform-schema-v1" ] } swc_ecma_quote = { version = "0.25.0", path = "../swc_ecma_quote", optional = true } swc_ecmascript = { version = "0.179.0", path = "../swc_ecmascript", features = ["utils", "visit", "rkyv-impl"] } diff --git a/crates/swc_plugin/src/lib.rs b/crates/swc_plugin/src/lib.rs index 2f32ccd8532..46eaacb00da 100644 --- a/crates/swc_plugin/src/lib.rs +++ b/crates/swc_plugin/src/lib.rs @@ -3,7 +3,10 @@ // Reexports pub use swc_common::{ chain, - plugin::{deserialize_from_ptr, PluginError, PluginSerializedBytes, VersionedSerializable}, + plugin::{ + deserialize_from_ptr, PluginError, PluginSerializedBytes, VersionedSerializable, + PLUGIN_TRANSFORM_AST_SCHEMA_VERSION, + }, }; pub mod comments { diff --git a/crates/swc_plugin_macro/src/lib.rs b/crates/swc_plugin_macro/src/lib.rs index 5a5b9281fc9..50258127e97 100644 --- a/crates/swc_plugin_macro/src/lib.rs +++ b/crates/swc_plugin_macro/src/lib.rs @@ -21,6 +21,8 @@ fn handle_func(func: ItemFn) -> TokenStream { let ident = func.sig.ident.clone(); let transform_process_impl_ident = Ident::new("__transform_plugin_process_impl", Span::call_site()); + let transform_schema_version_ident = + Ident::new("__get_transform_plugin_schema_version", Span::call_site()); let ret = quote! { #func @@ -55,6 +57,11 @@ fn handle_func(func: ItemFn) -> TokenStream { 1 } + #[no_mangle] + pub fn #transform_schema_version_ident() -> u32 { + swc_plugin::PLUGIN_TRANSFORM_AST_SCHEMA_VERSION + } + // Macro to allow compose plugin's transform function without manual pointer operation. // Internally it wraps pointer operation also bubbles up error in forms of PluginError. // There are some cases error won't be wrapped up however - for example, we expect diff --git a/crates/swc_plugin_runner/Cargo.toml b/crates/swc_plugin_runner/Cargo.toml index c815556a537..f8551e29057 100644 --- a/crates/swc_plugin_runner/Cargo.toml +++ b/crates/swc_plugin_runner/Cargo.toml @@ -19,6 +19,9 @@ filesystem_cache = ["wasmer-cache"] # Supports a cache allow to store wasm module in-memory. This avoids recompilation # to the same module in a single procress lifecycle. memory_cache = [] +plugin-transform-schema-v1 = [ + "swc_common/plugin-transform-schema-v1" +] [dependencies] anyhow = "1.0.42" @@ -28,7 +31,7 @@ serde = { version = "1.0.126", features = ["derive"] } serde_json = "1.0.64" swc_common = { version = "0.23.0", path = "../swc_common", features = [ "plugin-rt", - "concurrent", + "concurrent" ] } swc_ecma_ast = { version = "0.84.0", path = "../swc_ecma_ast", features = [ "rkyv-impl", diff --git a/crates/swc_plugin_runner/src/transform_executor.rs b/crates/swc_plugin_runner/src/transform_executor.rs index 3022b21a828..f32b1fa6a44 100644 --- a/crates/swc_plugin_runner/src/transform_executor.rs +++ b/crates/swc_plugin_runner/src/transform_executor.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use anyhow::{anyhow, Error}; use parking_lot::Mutex; use swc_common::{ - plugin::{PluginError, PluginSerializedBytes}, + plugin::{PluginError, PluginSerializedBytes, PLUGIN_TRANSFORM_AST_SCHEMA_VERSION}, SourceMap, }; use wasmer::Instance; @@ -14,6 +14,8 @@ use crate::memory_interop::write_into_memory_view; pub struct TransformExecutor { // Main transform interface plugin exports exported_plugin_transform: wasmer::NativeFunc<(i32, i32, i32, i32, i32, i32, i32), i32>, + // Schema version interface exports + exported_plugin_transform_schema_version: wasmer::NativeFunc<(), u32>, // `__free` function automatically exported via swc_plugin sdk to allow deallocation in guest // memory space exported_plugin_free: wasmer::NativeFunc<(i32, i32), i32>, @@ -42,6 +44,9 @@ impl TransformExecutor { .get_native_function::<(i32, i32, i32, i32, i32, i32, i32), i32>( "__transform_plugin_process_impl", )?, + exported_plugin_transform_schema_version: instance + .exports + .get_native_function::<(), u32>("__get_transform_plugin_schema_version")?, exported_plugin_free: instance .exports .get_native_function::<(i32, i32), i32>("__free")?, @@ -103,14 +108,28 @@ impl TransformExecutor { } /** - * Check compile-time versions of AST schema between the plugin and + * Check compile-time version of AST schema between the plugin and * the host. Returns true if it's compatible, false otherwise. * * Host should appropriately handle if plugin is not compatible to the * current runtime. */ - pub fn is_transform_schema_compatible(&self) -> bool { - todo!("Not supported yet"); + pub fn is_transform_schema_compatible(&self) -> Result { + let plugin_schema_version = self.exported_plugin_transform_schema_version.call(); + + match plugin_schema_version { + Ok(plugin_schema_version) => { + let host_schema_version = PLUGIN_TRANSFORM_AST_SCHEMA_VERSION; + + // TODO: this is incomplete + if plugin_schema_version == host_schema_version { + Ok(true) + } else { + Ok(false) + } + } + Err(e) => Err(anyhow!("Failed to call plugin's schema version: {}", e)), + } } #[tracing::instrument(level = "info", skip_all)] diff --git a/crates/swc_plugin_runner/tests/integration.rs b/crates/swc_plugin_runner/tests/integration.rs index 77430528ef3..af83c722568 100644 --- a/crates/swc_plugin_runner/tests/integration.rs +++ b/crates/swc_plugin_runner/tests/integration.rs @@ -24,6 +24,14 @@ fn build_plugin(dir: &Path) -> Result { cmd.args(["build", "--target=wasm32-wasi"]) .stderr(Stdio::inherit()); cmd.output()?; + + if !cmd + .status() + .expect("Exit code should be available") + .success() + { + return Err(anyhow!("Failed to build plugin")); + } } for entry in fs::read_dir(&dir.join("target").join("wasm32-wasi").join("debug"))? { diff --git a/tests/rust-plugins/swc_internal_plugin/Cargo.lock b/tests/rust-plugins/swc_internal_plugin/Cargo.lock index aacb83ede75..25f2b5ce57e 100644 --- a/tests/rust-plugins/swc_internal_plugin/Cargo.lock +++ b/tests/rust-plugins/swc_internal_plugin/Cargo.lock @@ -848,7 +848,7 @@ dependencies = [ [[package]] name = "swc_plugin" -version = "0.74.0" +version = "0.75.0" dependencies = [ "swc_atoms", "swc_common", @@ -868,7 +868,7 @@ dependencies = [ [[package]] name = "swc_plugin_proxy" -version = "0.8.0" +version = "0.9.0" dependencies = [ "better_scoped_tls", "bytecheck",