From 2dfef6b6c7b506bd64fbc68472aa8757b93de9fd Mon Sep 17 00:00:00 2001 From: FroyaTheHen Date: Wed, 11 Dec 2024 14:56:20 +0100 Subject: [PATCH 1/2] feat: executable plugin --- Cargo.lock | 26 +++++++++++++++++++ Cargo.toml | 1 + scarb/Cargo.toml | 1 + scarb/scarblib/executable/Scarb.toml | 6 +++++ scarb/src/compiler/compilers/executable.rs | 0 scarb/src/compiler/plugin/builtin.rs | 23 ++++++++++++++++ scarb/src/compiler/plugin/mod.rs | 3 ++- scarb/src/core/package/name.rs | 4 ++- scarb/src/lib.rs | 1 + scarb/src/ops/resolve.rs | 5 ++++ .../scarb-test-support/src/project_builder.rs | 4 +++ 11 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 scarb/scarblib/executable/Scarb.toml create mode 100644 scarb/src/compiler/compilers/executable.rs diff --git a/Cargo.lock b/Cargo.lock index 01c185e56..e2cf9944d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -662,6 +662,31 @@ dependencies = [ "good_lp", ] +[[package]] +name = "cairo-lang-executable" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cdfebefb3d4541b5daea229f1fe0c195c1153527e14a6246491327c0f44d837" +dependencies = [ + "anyhow", + "cairo-lang-casm", + "cairo-lang-compiler", + "cairo-lang-defs", + "cairo-lang-filesystem", + "cairo-lang-lowering", + "cairo-lang-plugins", + "cairo-lang-runnable-utils", + "cairo-lang-semantic", + "cairo-lang-sierra-generator", + "cairo-lang-sierra-to-casm", + "cairo-lang-syntax", + "cairo-lang-utils", + "cairo-vm", + "indoc", + "itertools 0.12.1", + "serde", +] + [[package]] name = "cairo-lang-filesystem" version = "2.9.1" @@ -4922,6 +4947,7 @@ dependencies = [ "cairo-lang-compiler", "cairo-lang-defs", "cairo-lang-diagnostics", + "cairo-lang-executable", "cairo-lang-filesystem", "cairo-lang-formatter", "cairo-lang-lowering", diff --git a/Cargo.toml b/Cargo.toml index 9646735af..532b2f501 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ cairo-lang-compiler = "*" cairo-lang-defs = "*" cairo-lang-diagnostics = "*" cairo-lang-doc = "*" +cairo-lang-executable = "*" cairo-lang-filesystem = "*" cairo-lang-formatter = "*" cairo-lang-lowering = "*" diff --git a/scarb/Cargo.toml b/scarb/Cargo.toml index d1d093ab7..50ad5100f 100644 --- a/scarb/Cargo.toml +++ b/scarb/Cargo.toml @@ -19,6 +19,7 @@ async-trait.workspace = true cairo-lang-compiler.workspace = true cairo-lang-defs.workspace = true cairo-lang-diagnostics.workspace = true +cairo-lang-executable.workspace = true cairo-lang-filesystem.workspace = true cairo-lang-formatter.workspace = true cairo-lang-lowering.workspace = true diff --git a/scarb/scarblib/executable/Scarb.toml b/scarb/scarblib/executable/Scarb.toml new file mode 100644 index 000000000..e7743e39b --- /dev/null +++ b/scarb/scarblib/executable/Scarb.toml @@ -0,0 +1,6 @@ +[package] +name = "executable" +version = "{{ CAIRO_VERSION }}" + +[cairo-plugin] +builtin = true diff --git a/scarb/src/compiler/compilers/executable.rs b/scarb/src/compiler/compilers/executable.rs new file mode 100644 index 000000000..e69de29bb diff --git a/scarb/src/compiler/plugin/builtin.rs b/scarb/src/compiler/plugin/builtin.rs index 32d71a816..a16eaa368 100644 --- a/scarb/src/compiler/plugin/builtin.rs +++ b/scarb/src/compiler/plugin/builtin.rs @@ -1,5 +1,6 @@ use anyhow::Result; use cairo_lang_defs::plugin::{MacroPlugin, MacroPluginMetadata, PluginResult}; +use cairo_lang_executable::plugin::executable_plugin_suite; use cairo_lang_semantic::plugin::PluginSuite; use cairo_lang_starknet::starknet_plugin_suite; use cairo_lang_syntax::node::ast::ModuleItem; @@ -32,6 +33,28 @@ impl CairoPluginInstance for BuiltinStarkNetPluginInstance { } } +pub struct BuiltinExecutablePlugin; +impl CairoPlugin for BuiltinExecutablePlugin { + fn id(&self) -> PackageId { + PackageId::new( + PackageName::EXECUTABLE, + crate::version::get().cairo.version.to_version().unwrap(), + SourceId::for_std(), + ) + } + + fn instantiate(&self) -> Result> { + Ok(Box::new(BuiltinExecutablePluginInstance)) + } +} + +struct BuiltinExecutablePluginInstance; +impl CairoPluginInstance for BuiltinExecutablePluginInstance { + fn plugin_suite(&self) -> PluginSuite { + executable_plugin_suite() + } +} + pub struct BuiltinTestPlugin; impl CairoPlugin for BuiltinTestPlugin { diff --git a/scarb/src/compiler/plugin/mod.rs b/scarb/src/compiler/plugin/mod.rs index 30838566f..86f6d153d 100644 --- a/scarb/src/compiler/plugin/mod.rs +++ b/scarb/src/compiler/plugin/mod.rs @@ -2,7 +2,7 @@ use std::collections::hash_map::Entry; use std::collections::HashMap; use std::fmt; -use crate::compiler::plugin::builtin::BuiltinTestAssertsPlugin; +use crate::compiler::plugin::builtin::{BuiltinExecutablePlugin, BuiltinTestAssertsPlugin}; use anyhow::{anyhow, bail, Result}; use cairo_lang_semantic::plugin::PluginSuite; use itertools::Itertools; @@ -63,6 +63,7 @@ impl CairoPluginRepository { // `starknet` package which makes it a dependency. This way we can deliver Starknet Cairo // library code to users etc. repo.add(Box::new(BuiltinStarkNetPlugin)).unwrap(); + repo.add(Box::new(BuiltinExecutablePlugin)).unwrap(); repo.add(Box::new(BuiltinTestPlugin)).unwrap(); repo.add(Box::new(BuiltinCairoRunPlugin)).unwrap(); repo.add(Box::new(BuiltinTestAssertsPlugin)).unwrap(); diff --git a/scarb/src/core/package/name.rs b/scarb/src/core/package/name.rs index c84ae2c83..66529887a 100644 --- a/scarb/src/core/package/name.rs +++ b/scarb/src/core/package/name.rs @@ -13,7 +13,8 @@ use crate::internal::restricted_names; use crate::core::Package; use crate::{ - CAIRO_RUN_PLUGIN_NAME, STARKNET_PLUGIN_NAME, TEST_ASSERTS_PLUGIN_NAME, TEST_PLUGIN_NAME, + CAIRO_RUN_PLUGIN_NAME, EXECUTABLE_PLUGIN_NAME, STARKNET_PLUGIN_NAME, TEST_ASSERTS_PLUGIN_NAME, + TEST_PLUGIN_NAME, }; /// A [`String`]-like type representing [`Package`] name. @@ -33,6 +34,7 @@ pub struct PackageName(SmolStr); impl PackageName { pub const CORE: Self = PackageName(SmolStr::new_inline(CORELIB_CRATE_NAME)); pub const STARKNET: Self = PackageName(SmolStr::new_inline(STARKNET_PLUGIN_NAME)); + pub const EXECUTABLE: Self = PackageName(SmolStr::new_inline(EXECUTABLE_PLUGIN_NAME)); pub const TEST_PLUGIN: Self = PackageName(SmolStr::new_inline(TEST_PLUGIN_NAME)); pub const CAIRO_RUN_PLUGIN: Self = PackageName(SmolStr::new_inline(CAIRO_RUN_PLUGIN_NAME)); pub const TEST_ASSERTS_PLUGIN: Self = diff --git a/scarb/src/lib.rs b/scarb/src/lib.rs index 673632428..d68fb8502 100644 --- a/scarb/src/lib.rs +++ b/scarb/src/lib.rs @@ -42,3 +42,4 @@ pub const TEST_ASSERTS_PLUGIN_NAME: &str = "assert_macros"; pub const CAIRO_RUN_PLUGIN_NAME: &str = "cairo_run"; pub const CARGO_MANIFEST_FILE_NAME: &str = "Cargo.toml"; pub const CARGO_LOCK_FILE_NAME: &str = "Cargo.lock"; +pub const EXECUTABLE_PLUGIN_NAME: &str = "executable"; diff --git a/scarb/src/ops/resolve.rs b/scarb/src/ops/resolve.rs index da0b99483..9840421bf 100644 --- a/scarb/src/ops/resolve.rs +++ b/scarb/src/ops/resolve.rs @@ -88,6 +88,11 @@ pub fn resolve_workspace_with_opts( .version_req(version_req.clone()) .source_id(SourceId::for_std()) .build(), + ManifestDependency::builder() + .name(PackageName::EXECUTABLE) + .version_req(version_req.clone()) + .source_id(SourceId::for_std()) + .build(), ManifestDependency::builder() .kind(DepKind::Target(TargetKind::TEST)) .name(PackageName::TEST_PLUGIN) diff --git a/utils/scarb-test-support/src/project_builder.rs b/utils/scarb-test-support/src/project_builder.rs index 2ca0dbee5..b243c3b0d 100644 --- a/utils/scarb-test-support/src/project_builder.rs +++ b/utils/scarb-test-support/src/project_builder.rs @@ -114,6 +114,10 @@ impl ProjectBuilder { self.dep_builtin("starknet") } + pub fn dep_executable(self) -> Self { + self.dep_builtin("executable") + } + pub fn dep_cairo_test(self) -> Self { self.dev_dep_builtin("cairo_test") } From 6c0c7421e95305118b8f1cf5d225678e321bbd6a Mon Sep 17 00:00:00 2001 From: FroyaTheHen Date: Wed, 11 Dec 2024 14:58:08 +0100 Subject: [PATCH 2/2] feat: compile executable --- scarb/src/compiler/compilers/executable.rs | 44 ++++++++++++++++++++++ scarb/src/compiler/compilers/mod.rs | 2 + scarb/src/compiler/repository.rs | 5 ++- scarb/src/core/manifest/target_kind.rs | 1 + scarb/tests/build_targets.rs | 29 ++++++++++++++ 5 files changed, 80 insertions(+), 1 deletion(-) diff --git a/scarb/src/compiler/compilers/executable.rs b/scarb/src/compiler/compilers/executable.rs index e69de29bb..244163da3 100644 --- a/scarb/src/compiler/compilers/executable.rs +++ b/scarb/src/compiler/compilers/executable.rs @@ -0,0 +1,44 @@ +use crate::compiler::helpers::write_json; +use crate::compiler::helpers::{build_compiler_config, collect_main_crate_ids}; +use crate::compiler::{CairoCompilationUnit, CompilationUnitAttributes, Compiler}; +use crate::core::{TargetKind, Workspace}; +use anyhow::Result; +use cairo_lang_compiler::db::RootDatabase; +use cairo_lang_executable::executable::Executable; +use tracing::trace_span; + +pub struct ExecutableCompiler; + +impl Compiler for ExecutableCompiler { + fn target_kind(&self) -> TargetKind { + TargetKind::EXECUTABLE.clone() + } + + fn compile( + &self, + unit: CairoCompilationUnit, + db: &mut RootDatabase, + ws: &Workspace<'_>, + ) -> Result<()> { + let target_dir = unit.target_dir(ws); + let main_crate_ids = collect_main_crate_ids(&unit, db); + let compiler_config = build_compiler_config(db, &unit, &main_crate_ids, ws); + let _ = trace_span!("compile_executable").enter(); + let executable = Executable::new( + cairo_lang_executable::compile::compile_executable_in_prepared_db( + db, + None, + main_crate_ids, + compiler_config.diagnostics_reporter, + )?, + ); + + write_json( + format!("{}.executable.json", unit.main_component().target_name()).as_str(), + "output file", + &target_dir, + ws, + &executable, + ) + } +} diff --git a/scarb/src/compiler/compilers/mod.rs b/scarb/src/compiler/compilers/mod.rs index 4d810b570..c268bdb22 100644 --- a/scarb/src/compiler/compilers/mod.rs +++ b/scarb/src/compiler/compilers/mod.rs @@ -1,7 +1,9 @@ +pub use executable::*; pub use lib::*; pub use starknet_contract::*; pub use test::*; +mod executable; mod lib; mod starknet_contract; mod test; diff --git a/scarb/src/compiler/repository.rs b/scarb/src/compiler/repository.rs index 523f5a5eb..0cc3c45d1 100644 --- a/scarb/src/compiler/repository.rs +++ b/scarb/src/compiler/repository.rs @@ -7,7 +7,9 @@ use cairo_lang_compiler::db::RootDatabase; use itertools::Itertools; use smol_str::SmolStr; -use crate::compiler::compilers::{LibCompiler, StarknetContractCompiler, TestCompiler}; +use crate::compiler::compilers::{ + ExecutableCompiler, LibCompiler, StarknetContractCompiler, TestCompiler, +}; use crate::compiler::{CairoCompilationUnit, CompilationUnitAttributes, Compiler}; use crate::core::Workspace; @@ -27,6 +29,7 @@ impl CompilerRepository { repo.add(Box::new(LibCompiler)).unwrap(); repo.add(Box::new(StarknetContractCompiler)).unwrap(); repo.add(Box::new(TestCompiler)).unwrap(); + repo.add(Box::new(ExecutableCompiler)).unwrap(); repo } diff --git a/scarb/src/core/manifest/target_kind.rs b/scarb/src/core/manifest/target_kind.rs index be789157d..0a99a385f 100644 --- a/scarb/src/core/manifest/target_kind.rs +++ b/scarb/src/core/manifest/target_kind.rs @@ -15,6 +15,7 @@ impl TargetKind { pub const LIB: Self = TargetKind(SmolStr::new_inline("lib")); pub const TEST: Self = TargetKind(SmolStr::new_inline("test")); pub const STARKNET_CONTRACT: Self = TargetKind(SmolStr::new_inline("starknet-contract")); + pub const EXECUTABLE: Self = TargetKind(SmolStr::new_inline("executable")); /// Constructs and validates new [`TargetKind`]. /// diff --git a/scarb/tests/build_targets.rs b/scarb/tests/build_targets.rs index 630e1547e..2bc20e3d9 100644 --- a/scarb/tests/build_targets.rs +++ b/scarb/tests/build_targets.rs @@ -1047,3 +1047,32 @@ fn test_target_builds_external() { t.child("hello/target/dev/hello_unittest.test.starknet_artifacts.json") .assert_is_json::(); } + +#[test] +fn test_executable_compiler_creates_output_files() { + let t = TempDir::new().unwrap(); + ProjectBuilder::start() + .name("executable_test") + .dep_cairo_test() + .dep_starknet() + .dep_executable() + .manifest_extra(indoc! {r#" + [[target.executable]] + "#}) + .lib_cairo(indoc! {r#" + #[executable] + fn main() -> felt252 { + 42 + } + "#}) + .build(&t); + + Scarb::quick_snapbox() + .arg("build") + .current_dir(&t) + .assert() + .success(); + + t.child("target/dev/executable_test.executable.json") + .assert(predicates::path::exists()); +}