diff --git a/scarb/src/compiler/compilation_unit.rs b/scarb/src/compiler/compilation_unit.rs index cf9fd55d8..b279c7f2f 100644 --- a/scarb/src/compiler/compilation_unit.rs +++ b/scarb/src/compiler/compilation_unit.rs @@ -91,6 +91,10 @@ impl CompilationUnit { ws.target_dir().child(self.profile.as_str()) } + pub fn is_cairo_plugin(&self) -> bool { + self.target().is_cairo_plugin() + } + pub fn is_sole_for_package(&self) -> bool { self.main_component() .package diff --git a/scarb/src/compiler/plugin/proc_macro/compilation.rs b/scarb/src/compiler/plugin/proc_macro/compilation.rs index be8cee29e..6806e3343 100644 --- a/scarb/src/compiler/plugin/proc_macro/compilation.rs +++ b/scarb/src/compiler/plugin/proc_macro/compilation.rs @@ -1,8 +1,13 @@ use crate::compiler::plugin::proc_macro::PROC_MACRO_BUILD_PROFILE; -use crate::core::{Config, Package}; +use crate::compiler::CompilationUnit; +use crate::core::{Config, Package, Workspace}; use crate::flock::Filesystem; +use crate::process::exec_piping; +use anyhow::Result; use camino::Utf8PathBuf; use libloading::library_filename; +use std::process::Command; +use tracing::trace_span; /// This trait is used to define the shared library path for a package. pub trait SharedLibraryProvider { @@ -38,3 +43,44 @@ impl SharedLibraryProvider for Package { .join(lib_name) } } + +pub fn compile_unit(unit: CompilationUnit, ws: &Workspace<'_>) -> Result<()> { + let main_package = unit.components.first().unwrap().package.clone(); + let cmd = CargoCommand { + current_dir: main_package.root().to_path_buf(), + target_dir: main_package + .target_path(ws.config()) + .path_unchecked() + .to_path_buf(), + }; + { + let _ = trace_span!("compile_proc_macro").enter(); + exec(&mut cmd.into(), ws.config())?; + } + Ok(()) +} + +struct CargoCommand { + current_dir: Utf8PathBuf, + target_dir: Utf8PathBuf, +} + +impl From for Command { + fn from(args: CargoCommand) -> Self { + let mut cmd = Command::new("cargo"); + cmd.current_dir(args.current_dir); + cmd.args(["build", "--release"]); + cmd.arg("--target-dir"); + cmd.arg(args.target_dir); + cmd + } +} + +fn exec(cmd: &mut Command, config: &Config) -> Result<()> { + exec_piping( + cmd, + config, + |line: &str| config.ui().print(line), + |line: &str| config.ui().print(line), + ) +} diff --git a/scarb/src/compiler/plugin/proc_macro/mod.rs b/scarb/src/compiler/plugin/proc_macro/mod.rs index 45cdadd18..4588446d1 100644 --- a/scarb/src/compiler/plugin/proc_macro/mod.rs +++ b/scarb/src/compiler/plugin/proc_macro/mod.rs @@ -2,5 +2,6 @@ pub mod compilation; mod ffi; mod host; +pub use compilation::compile_unit; pub use ffi::*; pub use host::*; diff --git a/scarb/src/ops/compile.rs b/scarb/src/ops/compile.rs index 93015fe80..aba52f77c 100644 --- a/scarb/src/ops/compile.rs +++ b/scarb/src/ops/compile.rs @@ -2,12 +2,14 @@ use anyhow::{anyhow, Result}; use cairo_lang_compiler::db::RootDatabase; use cairo_lang_compiler::diagnostics::DiagnosticsError; use indoc::formatdoc; +use itertools::Itertools; use scarb_ui::components::Status; use scarb_ui::HumanDuration; use crate::compiler::db::{build_scarb_root_database, has_starknet_plugin}; use crate::compiler::helpers::build_compiler_config; +use crate::compiler::plugin::proc_macro; use crate::compiler::CompilationUnit; use crate::core::{PackageId, PackageName, TargetKind, Utf8PathWorkspaceExt, Workspace}; use crate::ops; @@ -59,11 +61,21 @@ where let compilation_units = ops::generate_compilation_units(&resolve, ws)? .into_iter() - .filter(|cu| !opts.exclude_targets.contains(&cu.target().kind)) .filter(|cu| { - opts.include_targets.is_empty() || opts.include_targets.contains(&cu.target().kind) + let is_excluded = opts.exclude_targets.contains(&cu.target().kind); + let is_included = + opts.include_targets.is_empty() || opts.include_targets.contains(&cu.target().kind); + let is_selected = packages.contains(&cu.main_package_id); + let is_cairo_plugin = cu.components.first().unwrap().target.is_cairo_plugin(); + is_cairo_plugin || (is_selected && is_included && !is_excluded) + }) + .sorted_by_key(|cu| { + if cu.components.first().unwrap().target.is_cairo_plugin() { + 0 + } else { + 1 + } }) - .filter(|cu| packages.contains(&cu.main_package_id)) .collect::>(); for unit in compilation_units { @@ -89,22 +101,21 @@ fn compile_unit(unit: CompilationUnit, ws: &Workspace<'_>) -> Result<()> { .ui() .print(Status::new("Compiling", &unit.name())); - let mut db = build_scarb_root_database(&unit, ws)?; - - check_starknet_dependency(&unit, ws, &db, &package_name); - - ws.config() - .compilers() - .compile(unit, &mut db, ws) - .map_err(|err| { - if !suppress_error(&err) { - ws.config().ui().anyhow(&err); - } + let result = if unit.is_cairo_plugin() { + proc_macro::compile_unit(unit, ws) + } else { + let mut db = build_scarb_root_database(&unit, ws)?; + check_starknet_dependency(&unit, ws, &db, &package_name); + ws.config().compilers().compile(unit, &mut db, ws) + }; - anyhow!("could not compile `{package_name}` due to previous error") - })?; + result.map_err(|err| { + if !suppress_error(&err) { + ws.config().ui().anyhow(&err); + } - Ok(()) + anyhow!("could not compile `{package_name}` due to previous error") + }) } fn check_unit(unit: CompilationUnit, ws: &Workspace<'_>) -> Result<()> { diff --git a/scarb/src/ops/resolve.rs b/scarb/src/ops/resolve.rs index 40697a654..87c2ddcf5 100644 --- a/scarb/src/ops/resolve.rs +++ b/scarb/src/ops/resolve.rs @@ -155,12 +155,24 @@ pub fn generate_compilation_units( let mut units = Vec::with_capacity(ws.members().size_hint().0); for member in ws.members() { units.extend(if member.is_cairo_plugin() { - generate_cairo_plugin_compilation_units()? + generate_cairo_plugin_compilation_units(&member, ws)? } else { generate_cairo_compilation_units(&member, resolve, ws)? }); } + let cairo_plugins = units + .iter() + .flat_map(|unit| unit.cairo_plugins.clone()) + .filter(|plugin| !plugin.builtin) + .map(|plugin| plugin.package.clone()) + .unique_by(|plugin| plugin.id) + .collect_vec(); + + for plugin in cairo_plugins { + units.extend(generate_cairo_plugin_compilation_units(&plugin, ws)?); + } + assert!( units.iter().map(CompilationUnit::id).all_unique(), "All generated compilation units must have unique IDs." @@ -425,6 +437,24 @@ fn check_cairo_version_compatibility(packages: &[Package], ws: &Workspace<'_>) - Ok(()) } -fn generate_cairo_plugin_compilation_units() -> Result> { - bail!("compiling Cairo plugin packages is not possible yet") +fn generate_cairo_plugin_compilation_units( + member: &Package, + ws: &Workspace<'_>, +) -> Result> { + Ok(vec![CompilationUnit { + main_package_id: member.id, + components: vec![CompilationUnitComponent { + package: member.clone(), + cfg_set: None, + target: member + .fetch_target(&TargetKind::CAIRO_PLUGIN) + .cloned() + // Safe to unwrap, as member.is_cairo_plugin() has been ensured before. + .expect("main component of procedural macro must define `cairo-plugin` target"), + }], + cairo_plugins: Vec::new(), + profile: ws.current_profile()?, + compiler_config: member.manifest.compiler_config.clone(), + cfg_set: Default::default(), + }]) } diff --git a/scarb/src/process.rs b/scarb/src/process.rs index 0d4ed2a82..f90ab1237 100644 --- a/scarb/src/process.rs +++ b/scarb/src/process.rs @@ -87,9 +87,28 @@ mod imp { } } -/// Runs the process, waiting for completion, and mapping non-success exit codes to an error. #[tracing::instrument(level = "trace", skip_all)] pub fn exec(cmd: &mut Command, config: &Config) -> Result<()> { + exec_piping( + cmd, + config, + |line: &str| { + debug!("{line}"); + }, + |line: &str| { + debug!("{line}"); + }, + ) +} + +/// Runs the process, waiting for completion, and mapping non-success exit codes to an error. +#[tracing::instrument(level = "trace", skip_all)] +pub fn exec_piping( + cmd: &mut Command, + config: &Config, + stdout_callback: impl Fn(&str) + Send, + stderr_callback: impl Fn(&str) + Send, +) -> Result<()> { let cmd_str = shlex_join(cmd); config.ui().verbose(Status::new("Running", &cmd_str)); @@ -112,7 +131,7 @@ pub fn exec(cmd: &mut Command, config: &Config) -> Result<()> { let span = debug_span!("out"); move || { let mut stdout = stdout; - pipe_to_logs(&span, &mut stdout); + pipe(&span, &mut stdout, stdout_callback); } }); @@ -121,7 +140,7 @@ pub fn exec(cmd: &mut Command, config: &Config) -> Result<()> { let span = debug_span!("err"); move || { let mut stderr = stderr; - pipe_to_logs(&span, &mut stderr); + pipe(&span, &mut stderr, stderr_callback); } }); @@ -135,12 +154,12 @@ pub fn exec(cmd: &mut Command, config: &Config) -> Result<()> { } }); - fn pipe_to_logs(span: &Span, stream: &mut dyn Read) { + fn pipe(span: &Span, stream: &mut dyn Read, callback: impl Fn(&str)) { let _enter = span.enter(); let stream = BufReader::with_capacity(128, stream); for line in stream.lines() { match line { - Ok(line) => debug!("{line}"), + Ok(line) => callback(line.as_str()), Err(err) => warn!("{err:?}"), } } diff --git a/scarb/tests/build_cairo_plugin.rs b/scarb/tests/build_cairo_plugin.rs index f8308dd91..382b4cadf 100644 --- a/scarb/tests/build_cairo_plugin.rs +++ b/scarb/tests/build_cairo_plugin.rs @@ -5,6 +5,7 @@ use scarb_test_support::command::Scarb; use scarb_test_support::project_builder::ProjectBuilder; #[test] +#[ignore = "TODO(maciektr): Remove when proc-macros are implemented."] fn compile_cairo_plugin() { let t = TempDir::new().unwrap(); ProjectBuilder::start()