>();
+ // we have only 1 unit in projects
+ let unit = compilation_units.get(0).unwrap();
+ let db = build_scarb_root_database(unit, &ws).unwrap();
+ let dojo_metadata = dojo_metadata_from_workspace(&ws);
+ Ok(DevContext { db, unit: unit.clone(), ws, dojo_metadata })
+}
+
+fn build(context: &mut DevContext<'_>) -> Result<()> {
+ let ws = &context.ws;
+ let unit = &context.unit;
+ let package_name = unit.main_package_id.name.clone();
+ ws.config().compilers().compile(unit.clone(), &mut (context.db), ws).map_err(|err| {
+ ws.config().ui().anyhow(&err);
+
+ anyhow!("could not compile `{package_name}` due to previous error")
+ })?;
+ ws.config().ui().print("š¦ Rebuild done");
+ Ok(())
+}
+
+async fn migrate(
+ mut world_address: Option,
+ account: &SingleOwnerAccount,
+ name: Option,
+ ws: &Workspace<'_>,
+ previous_manifest: Option,
+) -> Result<(Manifest, Option)>
+where
+ P: Provider + Sync + Send + 'static,
+ S: Signer + Sync + Send + 'static,
+{
+ let target_dir = ws.target_dir().path_existent().unwrap();
+ let target_dir = target_dir.join(ws.config().profile().as_str());
+ let manifest_path = target_dir.join("manifest.json");
+ if !manifest_path.exists() {
+ return Err(anyhow!("manifest.json not found"));
+ }
+ let new_manifest = Manifest::load_from_path(manifest_path)?;
+ let diff = WorldDiff::compute(new_manifest.clone(), previous_manifest);
+ let total_diffs = diff.count_diffs();
+ let config = ws.config();
+ config.ui().print(format!("Total diffs found: {total_diffs}"));
+ if total_diffs == 0 {
+ return Ok((new_manifest, world_address));
+ }
+ match migration::apply_diff(
+ target_dir,
+ diff,
+ name.clone(),
+ world_address,
+ account,
+ config,
+ None,
+ )
+ .await
+ {
+ Ok(address) => {
+ config
+ .ui()
+ .print(format!("š World at address {} updated!", format_args!("{:#x}", address)));
+ world_address = Some(address);
+ }
+ Err(err) => {
+ config.ui().error(err.to_string());
+ return Err(err);
+ }
+ }
+
+ Ok((new_manifest, world_address))
+}
+
+fn process_event(event: &DebouncedEvent, context: &mut DevContext<'_>) -> DevAction {
+ let action = handle_event(event);
+ match &action {
+ DevAction::None => {}
+ DevAction::Build(path) => handle_build_action(path, context),
+ DevAction::Reload => {
+ handle_reload_action(context);
+ }
+ }
+ action
+}
+
+fn handle_build_action(path: &Path, context: &mut DevContext<'_>) {
+ context
+ .ws
+ .config()
+ .ui()
+ .print(format!("š¦ Need to rebuild {}", path.to_str().unwrap_or_default(),));
+ let db = &mut context.db;
+ let file = FileId::new(db, path.to_path_buf());
+ PrivRawFileContentQuery.in_db_mut(db.as_files_group_mut()).invalidate(&file);
+ db.override_file_content(file, None);
+}
+
+fn handle_reload_action(context: &mut DevContext<'_>) {
+ let config = context.ws.config();
+ config.ui().print("Reloading project");
+ let new_context = load_context(config).expect("Failed to load context");
+ let _ = mem::replace(context, new_context);
+}
+
+impl DevArgs {
+ pub fn run(self, config: &Config) -> Result<()> {
+ let mut context = load_context(config)?;
+ let (tx, rx) = channel();
+ let mut debouncer = new_debouncer(Duration::from_secs(1), None, tx)?;
+
+ debouncer.watcher().watch(
+ config.manifest_path().parent().unwrap().as_std_path(),
+ RecursiveMode::Recursive,
+ )?;
+ let name = self.name.clone();
+ let mut previous_manifest: Option = Option::None;
+ let result = build(&mut context);
+ let env_metadata = context.dojo_metadata.as_ref().and_then(|e| e.env.clone());
+
+ let Some((mut world_address, account)) = context
+ .ws
+ .config()
+ .tokio_handle()
+ .block_on(migration::setup_env(
+ self.account,
+ self.starknet,
+ self.world,
+ env_metadata.as_ref(),
+ config,
+ name.as_ref(),
+ ))
+ .ok()
+ else {
+ return Err(anyhow!("Failed to setup environment"));
+ };
+
+ match context.ws.config().tokio_handle().block_on(migrate(
+ world_address,
+ &account,
+ name.clone(),
+ &context.ws,
+ previous_manifest.clone(),
+ )) {
+ Ok((manifest, address)) => {
+ previous_manifest = Some(manifest);
+ world_address = address;
+ }
+ Err(error) => {
+ log::error!("Error: {error:?}");
+ }
+ }
+ loop {
+ let action = match rx.recv() {
+ Ok(Ok(events)) => events
+ .iter()
+ .map(|event| process_event(event, &mut context))
+ .last()
+ .unwrap_or(DevAction::None),
+ Ok(Err(_)) => DevAction::None,
+ Err(error) => {
+ log::error!("Error: {error:?}");
+ break;
+ }
+ };
+
+ if action != DevAction::None && build(&mut context).is_ok() {
+ match context.ws.config().tokio_handle().block_on(migrate(
+ world_address,
+ &account,
+ name.clone(),
+ &context.ws,
+ previous_manifest.clone(),
+ )) {
+ Ok((manifest, address)) => {
+ previous_manifest = Some(manifest);
+ world_address = address;
+ }
+ Err(error) => {
+ log::error!("Error: {error:?}");
+ }
+ }
+ }
+ }
+ result
+ }
+}
diff --git a/crates/sozo/src/commands/mod.rs b/crates/sozo/src/commands/mod.rs
index c4e9b7c4f5..9d3ce0dbd7 100644
--- a/crates/sozo/src/commands/mod.rs
+++ b/crates/sozo/src/commands/mod.rs
@@ -7,6 +7,7 @@ pub(crate) mod auth;
pub(crate) mod build;
pub(crate) mod completions;
pub(crate) mod component;
+pub(crate) mod dev;
pub(crate) mod events;
pub(crate) mod execute;
pub(crate) mod init;
@@ -16,13 +17,16 @@ pub(crate) mod register;
pub(crate) mod system;
pub(crate) mod test;
+// copy of non pub functions from scarb
+pub(crate) mod scarb_internal;
+
pub fn run(command: Commands, config: &Config) -> Result<()> {
match command {
Commands::Init(args) => args.run(config),
Commands::Test(args) => args.run(config),
Commands::Build(args) => args.run(config),
Commands::Migrate(args) => args.run(config),
-
+ Commands::Dev(args) => args.run(config),
Commands::Auth(args) => args.run(config),
Commands::Execute(args) => args.run(config),
Commands::Component(args) => args.run(config),
diff --git a/crates/sozo/src/commands/scarb_internal/mod.rs b/crates/sozo/src/commands/scarb_internal/mod.rs
new file mode 100644
index 0000000000..6f216c16eb
--- /dev/null
+++ b/crates/sozo/src/commands/scarb_internal/mod.rs
@@ -0,0 +1,55 @@
+// I have copied source code from https://github.com/software-mansion/scarb/blob/main/scarb/src/compiler/db.rs
+// since build_scarb_root_database is not public
+//
+// NOTE: This files needs to be updated whenever scarb version is updated
+use anyhow::Result;
+use cairo_lang_compiler::db::RootDatabase;
+use cairo_lang_compiler::project::{ProjectConfig, ProjectConfigContent};
+use cairo_lang_filesystem::ids::Directory;
+use scarb::compiler::CompilationUnit;
+use scarb::core::Workspace;
+use tracing::trace;
+
+// TODO(mkaput): ScarbDatabase?
+pub(crate) fn build_scarb_root_database(
+ unit: &CompilationUnit,
+ ws: &Workspace<'_>,
+) -> Result {
+ let mut b = RootDatabase::builder();
+ b.with_project_config(build_project_config(unit)?);
+ b.with_cfg(unit.cfg_set.clone());
+
+ for plugin_info in &unit.cairo_plugins {
+ let package_id = plugin_info.package.id;
+ let plugin = ws.config().cairo_plugins().fetch(package_id)?;
+ let instance = plugin.instantiate()?;
+ for macro_plugin in instance.macro_plugins() {
+ b.with_macro_plugin(macro_plugin);
+ }
+ for (name, inline_macro_plugin) in instance.inline_macro_plugins() {
+ b.with_inline_macro_plugin(&name, inline_macro_plugin);
+ }
+ }
+
+ b.build()
+}
+
+fn build_project_config(unit: &CompilationUnit) -> Result {
+ let crate_roots = unit
+ .components
+ .iter()
+ .filter(|component| !component.package.id.is_core())
+ .map(|component| (component.cairo_package_name(), component.target.source_root().into()))
+ .collect();
+
+ let corelib = Some(Directory::Real(unit.core_package_component().target.source_root().into()));
+
+ let content = ProjectConfigContent { crate_roots };
+
+ let project_config =
+ ProjectConfig { base_path: unit.main_component().package.root().into(), corelib, content };
+
+ trace!(?project_config);
+
+ Ok(project_config)
+}
diff --git a/crates/sozo/src/main.rs b/crates/sozo/src/main.rs
index 290b8af897..b21d47e172 100644
--- a/crates/sozo/src/main.rs
+++ b/crates/sozo/src/main.rs
@@ -30,8 +30,9 @@ fn cli_main(args: SozoArgs) -> Result<()> {
let mut compilers = CompilerRepository::std();
let cairo_plugins = CairoPluginRepository::new();
- if let Commands::Build(_) = &args.command {
- compilers.add(Box::new(DojoCompiler)).unwrap();
+ match &args.command {
+ Commands::Build(_) | Commands::Dev(_) => compilers.add(Box::new(DojoCompiler)).unwrap(),
+ _ => {}
}
let manifest_path = scarb::ops::find_manifest_path(args.manifest_path.as_deref())?;
diff --git a/crates/sozo/src/ops/migration/mod.rs b/crates/sozo/src/ops/migration/mod.rs
index 8b30381fdc..2c790f6282 100644
--- a/crates/sozo/src/ops/migration/mod.rs
+++ b/crates/sozo/src/ops/migration/mod.rs
@@ -65,41 +65,60 @@ where
if total_diffs == 0 {
config.ui().print("\nāØ No changes to be made. Remote World is already up to date!")
} else {
- // Prepare migration strategy based on the diff.
+ // Mirate according to the diff.
+ apply_diff(target_dir, diff, name, world_address, &account, config, Some(args.transaction))
+ .await?;
+ }
- let strategy = prepare_migration(target_dir, diff, name, world_address, config)?;
+ Ok(())
+}
- println!(" ");
+pub(crate) async fn apply_diff(
+ target_dir: U,
+ diff: WorldDiff,
+ name: Option,
+ world_address: Option,
+ account: &SingleOwnerAccount,
+ config: &Config,
+ txn_config: Option,
+) -> Result
+where
+ U: AsRef,
+ P: Provider + Sync + Send + 'static,
+ S: Signer + Sync + Send + 'static,
+{
+ let strategy = prepare_migration(target_dir, diff, name, world_address, config)?;
- let block_height = execute_strategy(&strategy, &account, config, Some(args.transaction))
- .await
- .map_err(|e| anyhow!(e))
- .with_context(|| "Problem trying to migrate.")?;
-
- if let Some(block_height) = block_height {
- config.ui().print(format!(
- "\nš Successfully migrated World on block #{} at address {}",
- block_height,
- bold_message(format!(
- "{:#x}",
- strategy.world_address().expect("world address must exist")
- ))
- ));
- } else {
- config.ui().print(format!(
- "\nš Successfully migrated World at address {}",
- bold_message(format!(
- "{:#x}",
- strategy.world_address().expect("world address must exist")
- ))
- ));
- }
+ println!(" ");
+
+ let block_height = execute_strategy(&strategy, account, config, txn_config)
+ .await
+ .map_err(|e| anyhow!(e))
+ .with_context(|| "Problem trying to migrate.")?;
+
+ if let Some(block_height) = block_height {
+ config.ui().print(format!(
+ "\nš Successfully migrated World on block #{} at address {}",
+ block_height,
+ bold_message(format!(
+ "{:#x}",
+ strategy.world_address().expect("world address must exist")
+ ))
+ ));
+ } else {
+ config.ui().print(format!(
+ "\nš Successfully migrated World at address {}",
+ bold_message(format!(
+ "{:#x}",
+ strategy.world_address().expect("world address must exist")
+ ))
+ ));
}
- Ok(())
+ strategy.world_address()
}
-async fn setup_env(
+pub(crate) async fn setup_env(
account: AccountOptions,
starknet: StarknetOptions,
world: WorldOptions,