diff --git a/Cargo.lock b/Cargo.lock index 0f15a63b6..06faaf0d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3299,6 +3299,16 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libloading" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "libmimalloc-sys" version = "0.1.35" @@ -4465,6 +4475,7 @@ dependencies = [ "io_tee", "itertools 0.12.1", "libc", + "libloading", "ntest", "once_cell", "pathdiff", diff --git a/Cargo.toml b/Cargo.toml index fd287c1af..693ffe76c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,6 +126,7 @@ xshell = "0.2" xxhash-rust = { version = "0.8", features = ["xxh3"] } zip = { version = "0.6", default-features = false, features = ["deflate"] } zstd = "0.13" +libloading = "0.8.1" [profile.release] lto = true diff --git a/scarb/Cargo.toml b/scarb/Cargo.toml index e6dd02936..bc8dca7d0 100644 --- a/scarb/Cargo.toml +++ b/scarb/Cargo.toml @@ -46,6 +46,7 @@ ignore.workspace = true include_dir.workspace = true indoc.workspace = true itertools.workspace = true +libloading.workspace = true once_cell.workspace = true pathdiff.workspace = true petgraph.workspace = true diff --git a/scarb/src/compiler/db.rs b/scarb/src/compiler/db.rs index 7eeba0611..424a60244 100644 --- a/scarb/src/compiler/db.rs +++ b/scarb/src/compiler/db.rs @@ -43,7 +43,7 @@ fn load_plugins( let instance = plugin.instantiate()?; builder.with_plugin_suite(instance.plugin_suite()); } else { - proc_macros.register(plugin_info.package.clone())?; + proc_macros.register(plugin_info.package.clone(), ws.config())?; } } builder.with_plugin_suite(proc_macros.into_plugin_suite()); diff --git a/scarb/src/compiler/plugin/proc_macro/compilation.rs b/scarb/src/compiler/plugin/proc_macro/compilation.rs new file mode 100644 index 000000000..7b6f8f262 --- /dev/null +++ b/scarb/src/compiler/plugin/proc_macro/compilation.rs @@ -0,0 +1,40 @@ +use crate::compiler::plugin::proc_macro::PROC_MACRO_BUILD_PROFILE; +use crate::core::{Config, Package}; +use crate::flock::Filesystem; +use camino::Utf8PathBuf; +use libloading::library_filename; + +/// This trait is used to define the shared library path for a package. +pub trait SharedLibraryProvider { + /// Location of Cargo `target` directory. + fn target_path(&self, config: &Config) -> Filesystem; + /// Location of the shared library for the package. + fn shared_lib_path(&self, config: &Config) -> Utf8PathBuf; +} + +impl SharedLibraryProvider for Package { + fn target_path(&self, config: &Config) -> Filesystem { + let ident = format!("{}-{}", self.id.name, self.id.source_id.ident()); + // Defines the Cargo target directory in cache, as: + // `/(..)/SCARB_CACHE/plugins/proc_macro/-/target/` + config + .dirs() + .procedural_macros_dir() + .into_child(ident) + .into_child(format!("v{}", self.id.version)) + .into_child("target") + } + + fn shared_lib_path(&self, config: &Config) -> Utf8PathBuf { + let lib_name = library_filename(self.id.name.to_string()); + let lib_name = lib_name + .into_string() + .expect("library name must be valid UTF-8"); + // Defines the shared library path inside the target directory, as: + // `/(..)/target/release/[lib].[so|dll|dylib]` + self.target_path(config) + .into_child(PROC_MACRO_BUILD_PROFILE) + .path_unchecked() + .join(lib_name) + } +} diff --git a/scarb/src/compiler/plugin/proc_macro/ffi.rs b/scarb/src/compiler/plugin/proc_macro/ffi.rs index 782bac885..9d8411562 100644 --- a/scarb/src/compiler/plugin/proc_macro/ffi.rs +++ b/scarb/src/compiler/plugin/proc_macro/ffi.rs @@ -1,11 +1,22 @@ -use crate::core::{Package, PackageId}; -use anyhow::Result; +use crate::core::{Config, Package, PackageId}; +use anyhow::{Context, Result}; use cairo_lang_defs::patcher::PatchBuilder; +use cairo_lang_macro::stable_abi::{StableProcMacroResult, StableTokenStream}; use cairo_lang_macro::{ProcMacroResult, TokenStream}; use cairo_lang_syntax::node::db::SyntaxGroup; use cairo_lang_syntax::node::{ast, TypedSyntaxNode}; +use camino::Utf8PathBuf; +use libloading::{Library, Symbol}; use std::fmt::Debug; +use crate::compiler::plugin::proc_macro::compilation::SharedLibraryProvider; +#[cfg(not(windows))] +use libloading::os::unix::Symbol as RawSymbol; +#[cfg(windows)] +use libloading::os::windows::Symbol as RawSymbol; + +pub const PROC_MACRO_BUILD_PROFILE: &str = "release"; + pub trait FromItemAst { fn from_item_ast(db: &dyn SyntaxGroup, item_ast: ast::ModuleItem) -> Self; } @@ -22,9 +33,17 @@ impl FromItemAst for TokenStream { /// /// This struct is a wrapper around a shared library containing the procedural macro implementation. /// It is responsible for loading the shared library and providing a safe interface for code expansion. -#[derive(Debug, Clone)] pub struct ProcMacroInstance { package_id: PackageId, + plugin: Plugin, +} + +impl Debug for ProcMacroInstance { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ProcMacroInstance") + .field("package_id", &self.package_id) + .finish() + } } impl ProcMacroInstance { @@ -32,21 +51,54 @@ impl ProcMacroInstance { self.package_id } - pub fn try_new(package: Package) -> Result { - // Load shared library - // TODO(maciektr): Implement + /// Load shared library + pub fn try_new(package: Package, config: &Config) -> Result { + let lib_path = package.shared_lib_path(config); + let plugin = unsafe { Plugin::try_new(lib_path.to_path_buf())? }; Ok(Self { + plugin, package_id: package.id, }) } - pub fn declared_attributes(&self) -> Vec { vec![self.package_id.name.to_string()] } - pub(crate) fn generate_code(&self, _token_stream: TokenStream) -> ProcMacroResult { - // Apply expansion to token stream. - // TODO(maciektr): Implement - ProcMacroResult::Leave + /// Apply expansion to token stream. + pub(crate) fn generate_code(&self, token_stream: TokenStream) -> ProcMacroResult { + let ffi_token_stream = unsafe { StableTokenStream::from_token_stream(token_stream) }; + let result = (self.plugin.vtable.expand)(ffi_token_stream); + unsafe { result.into_proc_macro_result() } + } +} + +type ExpandCode = extern "C" fn(StableTokenStream) -> StableProcMacroResult; + +struct VTableV0 { + expand: RawSymbol, +} + +impl VTableV0 { + unsafe fn try_new(library: &Library) -> Result { + let expand: Symbol<'_, ExpandCode> = library + .get(b"expand\0") + .context("failed to load expand function for procedural macro")?; + let expand = expand.into_raw(); + Ok(VTableV0 { expand }) + } +} + +struct Plugin { + #[allow(dead_code)] + library: Library, + vtable: VTableV0, +} + +impl Plugin { + unsafe fn try_new(library_path: Utf8PathBuf) -> Result { + let library = Library::new(library_path)?; + let vtable = VTableV0::try_new(&library)?; + + Ok(Plugin { library, vtable }) } } diff --git a/scarb/src/compiler/plugin/proc_macro/host.rs b/scarb/src/compiler/plugin/proc_macro/host.rs index 992baa2ca..e7b7835db 100644 --- a/scarb/src/compiler/plugin/proc_macro/host.rs +++ b/scarb/src/compiler/plugin/proc_macro/host.rs @@ -1,5 +1,5 @@ use crate::compiler::plugin::proc_macro::{FromItemAst, ProcMacroInstance}; -use crate::core::{Package, PackageId}; +use crate::core::{Config, Package, PackageId}; use anyhow::Result; use cairo_lang_defs::plugin::{ MacroPlugin, MacroPluginMetadata, PluginGeneratedFile, PluginResult, @@ -178,10 +178,8 @@ pub struct ProcMacroHost { } impl ProcMacroHost { - pub fn register(&mut self, package: Package) -> Result<()> { - // Create instance - // Register instance in hash map - let instance = ProcMacroInstance::try_new(package)?; + pub fn register(&mut self, package: Package, config: &Config) -> Result<()> { + let instance = ProcMacroInstance::try_new(package, config)?; self.macros.push(Arc::new(instance)); Ok(()) } diff --git a/scarb/src/compiler/plugin/proc_macro/mod.rs b/scarb/src/compiler/plugin/proc_macro/mod.rs index 7e563afe8..45cdadd18 100644 --- a/scarb/src/compiler/plugin/proc_macro/mod.rs +++ b/scarb/src/compiler/plugin/proc_macro/mod.rs @@ -1,3 +1,4 @@ +pub mod compilation; mod ffi; mod host; diff --git a/scarb/src/core/dirs.rs b/scarb/src/core/dirs.rs index 39c73e6e5..d2bd67e14 100644 --- a/scarb/src/core/dirs.rs +++ b/scarb/src/core/dirs.rs @@ -70,6 +70,10 @@ impl AppDirs { pub fn registry_dir(&self) -> Filesystem { self.cache_dir.child("registry") } + + pub fn procedural_macros_dir(&self) -> Filesystem { + self.cache_dir.child("plugins").child("proc_macro") + } } impl fmt::Display for AppDirs {