-
Notifications
You must be signed in to change notification settings - Fork 195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(sozo): add sozo dev
command
#2664
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,20 @@ | ||
use std::sync::mpsc::channel; | ||
use std::time::Duration; | ||
use std::thread; | ||
use std::time::{Duration, Instant}; | ||
|
||
use anyhow::Result; | ||
use clap::Args; | ||
use notify::event::Event; | ||
use notify::{EventKind, PollWatcher, RecursiveMode, Watcher}; | ||
use scarb::core::Config; | ||
use scarb_ui::args::{FeaturesSpec, PackagesFilter}; | ||
use tracing::{error, info, trace}; | ||
|
||
use super::build::BuildArgs; | ||
use super::migrate::MigrateArgs; | ||
use super::options::account::AccountOptions; | ||
use super::options::starknet::StarknetOptions; | ||
use super::options::transaction::TransactionOptions; | ||
use super::options::world::WorldOptions; | ||
|
||
#[derive(Debug, Args)] | ||
|
@@ -24,80 +27,140 @@ | |
|
||
#[command(flatten)] | ||
pub account: AccountOptions, | ||
|
||
#[command(flatten)] | ||
pub transaction: TransactionOptions, | ||
|
||
#[arg(long)] | ||
#[arg(help = "Generate Typescript bindings.")] | ||
pub typescript: bool, | ||
|
||
#[arg(long)] | ||
#[arg(help = "Generate Typescript bindings.")] | ||
pub typescript_v2: bool, | ||
|
||
#[arg(long)] | ||
#[arg(help = "Generate Unity bindings.")] | ||
pub unity: bool, | ||
|
||
#[arg(long)] | ||
#[arg(help = "Output directory.", default_value = "bindings")] | ||
pub bindings_output: String, | ||
|
||
/// Specify the features to activate. | ||
#[command(flatten)] | ||
pub features: FeaturesSpec, | ||
|
||
/// Specify packages to build. | ||
#[command(flatten)] | ||
pub packages: Option<PackagesFilter>, | ||
} | ||
|
||
impl DevArgs { | ||
/// Watches the `src` directory that is found at the same level of the `Scarb.toml` manifest | ||
/// of the project into the provided [`Config`]. | ||
/// | ||
/// When a change is detected, it rebuilds the project and applies the migrations. | ||
pub fn run(self, config: &Config) -> Result<()> { | ||
let (tx, rx) = channel(); | ||
let (file_tx, file_rx) = channel(); | ||
let (rebuild_tx, rebuild_rx) = channel(); | ||
|
||
let watcher_config = notify::Config::default().with_poll_interval(Duration::from_secs(1)); | ||
let watcher_config = | ||
notify::Config::default().with_poll_interval(Duration::from_millis(500)); | ||
|
||
let mut watcher = PollWatcher::new(tx, watcher_config)?; | ||
let mut watcher = PollWatcher::new(file_tx, watcher_config)?; | ||
|
||
let watched_directory = config.manifest_path().parent().unwrap().join("src"); | ||
|
||
watcher.watch(watched_directory.as_std_path(), RecursiveMode::Recursive).unwrap(); | ||
|
||
// Always build the project before starting the dev loop to make sure that the project is | ||
// in a valid state. Devs may not use `build` anymore when using `dev`. | ||
BuildArgs::default().run(config)?; | ||
// Initial build and migrate | ||
let build_args = BuildArgs { | ||
typescript: self.typescript, | ||
typescript_v2: self.typescript_v2, | ||
unity: self.unity, | ||
bindings_output: self.bindings_output, | ||
features: self.features, | ||
packages: self.packages, | ||
..Default::default() | ||
}; | ||
build_args.clone().run(config)?; | ||
info!("Initial build completed."); | ||
|
||
let _ = | ||
MigrateArgs::new_apply(self.world.clone(), self.starknet.clone(), self.account.clone()) | ||
.run(config); | ||
let migrate_args = MigrateArgs { | ||
world: self.world, | ||
starknet: self.starknet, | ||
account: self.account, | ||
transaction: self.transaction, | ||
}; | ||
|
||
let _ = migrate_args.clone().run(config); | ||
|
||
info!( | ||
directory = watched_directory.to_string(), | ||
"Initial migration completed. Waiting for changes." | ||
); | ||
|
||
let mut e_handler = EventHandler; | ||
|
||
loop { | ||
let is_rebuild_needed = match rx.recv() { | ||
Ok(maybe_event) => match maybe_event { | ||
Ok(event) => e_handler.process_event(event), | ||
let e_handler = EventHandler; | ||
let rebuild_tx_clone = rebuild_tx.clone(); | ||
|
||
// Independent thread to handle file events and trigger rebuilds. | ||
config.tokio_handle().spawn(async move { | ||
loop { | ||
match file_rx.recv() { | ||
Ok(maybe_event) => match maybe_event { | ||
Ok(event) => { | ||
trace!(?event, "Event received."); | ||
|
||
if e_handler.process_event(event) && rebuild_tx_clone.send(()).is_err() | ||
{ | ||
break; | ||
} | ||
} | ||
Err(error) => { | ||
error!(?error, "Processing event."); | ||
break; | ||
} | ||
}, | ||
Err(error) => { | ||
error!(?error, "Processing event."); | ||
error!(?error, "Receiving event."); | ||
break; | ||
} | ||
}, | ||
Err(error) => { | ||
error!(?error, "Receiving event."); | ||
break; | ||
} | ||
}; | ||
|
||
if is_rebuild_needed { | ||
// Ignore the fails of those commands as the `run` function | ||
// already logs the error. | ||
let _ = BuildArgs::default().run(config); | ||
|
||
let _ = MigrateArgs::new_apply( | ||
self.world.clone(), | ||
self.starknet.clone(), | ||
self.account.clone(), | ||
) | ||
.run(config); | ||
} | ||
}); | ||
Comment on lines
+99
to
+126
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Thread and channel cleanup needed in dev command Ohayo sensei! After analyzing the code, there are indeed concerns about thread and channel management:
To fix this, consider:
🔗 Analysis chainVerify proper closure of threads and channels. The spawned thread may outlive the Run the following script to check for proper thread and channel management: 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Verify that all threads are joined and channels are closed appropriately.
# Expected: No threads left running unexpectedly; channels are properly closed.
# Check for thread joins
rg 'spawn' -A 5 | rg -q 'join\(\)' && echo "All threads are joined." || echo "Threads may not be properly joined."
# Check for channel closure
rg 'channel' -A 5 | rg -q 'drop\(' && echo "Channels are properly closed." || echo "Channels may not be properly closed."
Length of output: 233 Script: #!/bin/bash
# Let's check the full context of the thread spawning and channel usage in dev.rs
rg -l "spawn|channel" bin/sozo/src/commands/dev.rs
# If found, let's see the full implementation
cat bin/sozo/src/commands/dev.rs
# Let's also check for any cleanup or drop implementations
ast-grep --pattern 'impl Drop for $_ { $$ }'
# Check for any shutdown or cleanup related functions
rg -A 5 "shutdown|cleanup|drop|close" bin/sozo/src/commands/dev.rs
Length of output: 6439 |
||
|
||
// Main thread handles the rebuilds. | ||
let mut last_event_time = None; | ||
let debounce_period = Duration::from_millis(1500); | ||
|
||
loop { | ||
match rebuild_rx.try_recv() { | ||
Ok(()) => { | ||
last_event_time = Some(Instant::now()); | ||
} | ||
Err(std::sync::mpsc::TryRecvError::Empty) => { | ||
if let Some(last_time) = last_event_time { | ||
if last_time.elapsed() >= debounce_period { | ||
let _ = build_args.clone().run(config); | ||
let _ = migrate_args.clone().run(config); | ||
last_event_time = None; | ||
} else { | ||
trace!("Change detected, waiting for debounce period."); | ||
} | ||
} | ||
thread::sleep(Duration::from_millis(300)); | ||
} | ||
Err(std::sync::mpsc::TryRecvError::Disconnected) => break, | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
#[derive(Debug, Default)] | ||
#[derive(Debug)] | ||
struct EventHandler; | ||
|
||
impl EventHandler { | ||
/// Processes a debounced event and return true if a rebuild is needed. | ||
/// Only considers Cairo file and the Scarb.toml manifest. | ||
fn process_event(&mut self, event: Event) -> bool { | ||
fn process_event(&self, event: Event) -> bool { | ||
trace!(?event, "Processing event."); | ||
|
||
let paths = match event.kind { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohayo, sensei! Ensure errors in build and migrate steps are handled.
The
run
methods forbuild_args
andmigrate_args
discard errors usinglet _ =
. Handling these errors will provide feedback if these processes fail, improving robustness.Apply this diff to properly handle errors: