From f49037fe124f1d187d1d586655cb17fd928464d5 Mon Sep 17 00:00:00 2001 From: Paul Buschmann Date: Sat, 26 Jul 2025 15:49:50 +0200 Subject: [PATCH 1/2] refactor: use PathBuf for folder arg --- rewatch/src/cli.rs | 10 +++++++--- rewatch/src/lock.rs | 4 ++-- rewatch/src/main.rs | 11 +++-------- rewatch/src/watcher.rs | 8 +++----- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/rewatch/src/cli.rs b/rewatch/src/cli.rs index 5a66fe0df1..68fa44a0a9 100644 --- a/rewatch/src/cli.rs +++ b/rewatch/src/cli.rs @@ -1,4 +1,8 @@ -use std::{ffi::OsString, ops::Deref}; +use std::{ + ffi::OsString, + ops::Deref, + path::{Path, PathBuf}, +}; use clap::{Args, Parser, Subcommand}; use clap_verbosity_flag::InfoLevel; @@ -45,7 +49,7 @@ pub struct Cli { pub struct FolderArg { /// The relative path to where the main rescript.json resides. IE - the root of your project. #[arg(default_value = ".")] - pub folder: String, + pub folder: PathBuf, } #[derive(Args, Debug, Clone)] @@ -219,7 +223,7 @@ pub enum Command { } impl Deref for FolderArg { - type Target = str; + type Target = Path; fn deref(&self) -> &Self::Target { &self.folder diff --git a/rewatch/src/lock.rs b/rewatch/src/lock.rs index e12306e96f..0af693d256 100644 --- a/rewatch/src/lock.rs +++ b/rewatch/src/lock.rs @@ -59,8 +59,8 @@ fn create(lockfile_location: &Path, pid: u32) -> Lock { .unwrap_or_else(|e| Lock::Error(Error::WritingLockfile(e))) } -pub fn get(folder: &str) -> Lock { - let location = Path::new(folder).join("lib").join(LOCKFILE); +pub fn get(folder: &Path) -> Lock { + let location = folder.join("lib").join(LOCKFILE); let pid = process::id(); match fs::read_to_string(&location) { diff --git a/rewatch/src/main.rs b/rewatch/src/main.rs index 5fc08d4250..4d05ca5344 100644 --- a/rewatch/src/main.rs +++ b/rewatch/src/main.rs @@ -39,7 +39,7 @@ fn main() -> Result<()> { match build::build( &build_args.filter, - Path::new(&build_args.folder as &str), + &build_args.folder, show_progress, build_args.no_timing, *build_args.create_sourcedirs, @@ -80,12 +80,7 @@ fn main() -> Result<()> { } => { let _lock = get_lock(&folder); - build::clean::clean( - Path::new(&folder as &str), - show_progress, - *snapshot_output, - dev.dev, - ) + build::clean::clean(&folder, show_progress, *snapshot_output, dev.dev) } cli::Command::Legacy { legacy_args } => { let code = build::pass_through_legacy(legacy_args); @@ -100,7 +95,7 @@ fn main() -> Result<()> { } } -fn get_lock(folder: &str) -> lock::Lock { +fn get_lock(folder: &Path) -> lock::Lock { match lock::get(folder) { lock::Lock::Error(error) => { println!("Could not start ReScript build: {error}"); diff --git a/rewatch/src/watcher.rs b/rewatch/src/watcher.rs index 09b1c6bc15..0039d89abc 100644 --- a/rewatch/src/watcher.rs +++ b/rewatch/src/watcher.rs @@ -311,7 +311,7 @@ async fn async_watch( pub fn start( filter: &Option, show_progress: bool, - folder: &str, + folder: &Path, after_build: Option, create_sourcedirs: bool, build_dev_deps: bool, @@ -325,17 +325,15 @@ pub fn start( let mut watcher = RecommendedWatcher::new(move |res| producer.push(res), Config::default()) .expect("Could not create watcher"); - log::debug!("watching {folder}"); + log::debug!("watching {}", folder.display()); watcher .watch(folder.as_ref(), RecursiveMode::Recursive) .expect("Could not start watcher"); - let path = Path::new(folder); - if let Err(e) = async_watch(AsyncWatchArgs { q: consumer, - path, + path: folder, show_progress, filter, after_build, From db4344e0a7dd72cbc6de661edd053f11db197775 Mon Sep 17 00:00:00 2001 From: Paul Buschmann Date: Sat, 26 Jul 2025 17:05:39 +0200 Subject: [PATCH 2/2] feat: clean on build_metadata change --- rewatch/src/build_metadata.rs | 68 +++++++++++++++++++++++++++++++++++ rewatch/src/config.rs | 20 +++++------ rewatch/src/lib.rs | 1 + rewatch/src/main.rs | 64 +++++++++++++++++++++++++++++---- rewatch/src/watcher.rs | 7 ++-- 5 files changed, 139 insertions(+), 21 deletions(-) create mode 100644 rewatch/src/build_metadata.rs diff --git a/rewatch/src/build_metadata.rs b/rewatch/src/build_metadata.rs new file mode 100644 index 0000000000..a5fb3e6896 --- /dev/null +++ b/rewatch/src/build_metadata.rs @@ -0,0 +1,68 @@ +use std::{ + fs, + hash::{DefaultHasher, Hash, Hasher}, + path::Path, +}; + +use crate::{config::Config, helpers, project_context::ProjectContext}; + +pub struct BuildMetadata { + pub version: String, + pub config_hash: String, +} + +impl BuildMetadata { + pub fn new(version: &str, config: &Config) -> Self { + let mut default_hasher = DefaultHasher::new(); + config.hash(&mut default_hasher); + let config_hash = default_hasher.finish().to_string(); + Self { + version: version.to_string(), + config_hash, + } + } +} + +const BUILD_METADATA_FILE_NAME: &str = ".build-metadata"; + +pub fn read_build_metadata(path: &Path) -> Option { + let file_path = path.join(BUILD_METADATA_FILE_NAME); + let file_content = fs::read_to_string(file_path).ok()?; + let file_content = file_content.trim(); + + let mut lines = file_content.lines(); + let version = lines.next()?.to_owned(); + let config_hash = lines.next()?.to_owned(); + + Some(BuildMetadata { version, config_hash }) +} + +pub fn current_build_metadata(path: &Path, version: &str) -> BuildMetadata { + let config = helpers::get_nearest_config(&path).expect("Couldn't find package root"); + let project_context = ProjectContext::new(&config).expect("Couldn't create project context"); + + BuildMetadata::new(version, project_context.get_root_config()) +} + +pub fn is_build_metadata_different( + last_build_metadata: Option<&BuildMetadata>, + current_build_metadata: &BuildMetadata, +) -> bool { + match last_build_metadata { + Some(last_metadata) => { + last_metadata.version != current_build_metadata.version + || last_metadata.config_hash != current_build_metadata.config_hash + } + None => true, + } +} + +pub fn write_build_metadata(path: &Path, build_metadata: &BuildMetadata) { + let file_path = path.join(BUILD_METADATA_FILE_NAME); + let mut file_content = String::from(""); + + file_content.push_str(&format!("{}\n", build_metadata.version)); + file_content.push_str(&format!("{}\n", build_metadata.config_hash)); + + fs::write(file_path, file_content).unwrap(); +} diff --git a/rewatch/src/config.rs b/rewatch/src/config.rs index 3435859e4b..b3555de2a6 100644 --- a/rewatch/src/config.rs +++ b/rewatch/src/config.rs @@ -6,7 +6,7 @@ use serde::Deserialize; use std::fs; use std::path::{Path, PathBuf}; -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, Hash)] #[serde(untagged)] pub enum OneOrMore { Multiple(Vec), @@ -141,7 +141,7 @@ impl Source { impl Eq for Source {} -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, Hash)] pub struct PackageSpec { pub module: String, #[serde(rename = "in-source", default = "default_true")] @@ -167,34 +167,34 @@ impl PackageSpec { } } -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, Hash)] #[serde(untagged)] pub enum Error { Catchall(bool), Qualified(String), } -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, Hash)] pub struct Warnings { pub number: Option, pub error: Option, } -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, Hash)] #[serde(untagged)] pub enum NamespaceConfig { Bool(bool), String(String), } -#[derive(Deserialize, Debug, Clone, Eq, PartialEq)] +#[derive(Deserialize, Debug, Clone, Eq, PartialEq, Hash)] #[serde(rename_all = "camelCase")] pub enum JsxMode { Classic, Automatic, } -#[derive(Deserialize, Debug, Clone, Eq, PartialEq)] +#[derive(Deserialize, Debug, Clone, Eq, PartialEq, Hash)] #[serde(rename_all = "camelCase")] #[serde(untagged)] pub enum JsxModule { @@ -202,7 +202,7 @@ pub enum JsxModule { Other(String), } -#[derive(Deserialize, Debug, Clone, Eq, PartialEq)] +#[derive(Deserialize, Debug, Clone, Eq, PartialEq, Hash)] pub struct JsxSpecs { pub version: Option, pub module: Option, @@ -215,7 +215,7 @@ pub struct JsxSpecs { /// We do not care about the internal structure because the gentype config is loaded by bsc. pub type GenTypeConfig = serde_json::Value; -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Hash)] pub enum DeprecationWarning { BsDependencies, BsDevDependencies, @@ -224,7 +224,7 @@ pub enum DeprecationWarning { /// # rescript.json representation /// This is tricky, there is a lot of ambiguity. This is probably incomplete. -#[derive(Deserialize, Debug, Clone, Default)] +#[derive(Deserialize, Debug, Clone, Hash, Default)] pub struct Config { pub name: String, // In the case of monorepos, the root source won't necessarily have to have sources. It can diff --git a/rewatch/src/lib.rs b/rewatch/src/lib.rs index a389e8172e..734a50e246 100644 --- a/rewatch/src/lib.rs +++ b/rewatch/src/lib.rs @@ -1,4 +1,5 @@ pub mod build; +pub mod build_metadata; pub mod cli; pub mod cmd; pub mod config; diff --git a/rewatch/src/main.rs b/rewatch/src/main.rs index 4d05ca5344..cb0b1c30e4 100644 --- a/rewatch/src/main.rs +++ b/rewatch/src/main.rs @@ -1,11 +1,13 @@ use anyhow::Result; -use clap::Parser; +use clap::{CommandFactory, Parser}; use log::LevelFilter; use std::{io::Write, path::Path}; -use rescript::{build, cli, cmd, format, lock, watcher}; +use rescript::{build, build_metadata, cli, cmd, format, lock, watcher}; fn main() -> Result<()> { + let cli_as_command = cli::Cli::command(); + let version = cli_as_command.get_version().expect("version is set"); let args = cli::Cli::parse(); let log_level_filter = args.verbose.log_level_filter(); @@ -37,6 +39,21 @@ fn main() -> Result<()> { cli::Command::Build(build_args) => { let _lock = get_lock(&build_args.folder); + if is_clean_build_needed(&build_args.folder, version) { + match build::clean::clean( + &build_args.folder, + show_progress, + *build_args.snapshot_output, + *build_args.dev, + ) { + Ok(_) => {} + Err(e) => { + println!("{e}"); + std::process::exit(1); + } + } + } + match build::build( &build_args.filter, &build_args.folder, @@ -54,6 +71,10 @@ fn main() -> Result<()> { if let Some(args_after_build) = (*build_args.after_build).clone() { cmd::run(args_after_build) } + build_metadata::write_build_metadata( + &build_args.folder.join("lib"), + &build_metadata::current_build_metadata(&build_args.folder, version), + ); std::process::exit(0) } }; @@ -61,7 +82,22 @@ fn main() -> Result<()> { cli::Command::Watch(watch_args) => { let _lock = get_lock(&watch_args.folder); - watcher::start( + if is_clean_build_needed(&watch_args.folder, version) { + match build::clean::clean( + &watch_args.folder, + show_progress, + *watch_args.snapshot_output, + *watch_args.dev, + ) { + Ok(_) => {} + Err(e) => { + println!("{e}"); + std::process::exit(1); + } + } + } + + match watcher::start( &watch_args.filter, show_progress, &watch_args.folder, @@ -69,9 +105,19 @@ fn main() -> Result<()> { *watch_args.create_sourcedirs, *watch_args.dev, *watch_args.snapshot_output, - ); - - Ok(()) + ) { + Ok(_) => { + build_metadata::write_build_metadata( + &watch_args.folder.join("lib"), + &build_metadata::current_build_metadata(&watch_args.folder, version), + ); + std::process::exit(0) + } + Err(e) => { + println!("{e:?}"); + std::process::exit(1) + } + } } cli::Command::Clean { folder, @@ -95,6 +141,12 @@ fn main() -> Result<()> { } } +fn is_clean_build_needed(path: &Path, version: &str) -> bool { + let last_build_metadata = build_metadata::read_build_metadata(path); + let current_build_metadata = build_metadata::current_build_metadata(path, version); + build_metadata::is_build_metadata_different(last_build_metadata.as_ref(), ¤t_build_metadata) +} + fn get_lock(folder: &Path) -> lock::Lock { match lock::get(folder) { lock::Lock::Error(error) => { diff --git a/rewatch/src/watcher.rs b/rewatch/src/watcher.rs index 0039d89abc..c72df01037 100644 --- a/rewatch/src/watcher.rs +++ b/rewatch/src/watcher.rs @@ -316,7 +316,7 @@ pub fn start( create_sourcedirs: bool, build_dev_deps: bool, snapshot_output: bool, -) { +) -> Result<(), Error> { futures::executor::block_on(async { let queue = Arc::new(FifoQueue::>::new()); let producer = queue.clone(); @@ -331,7 +331,7 @@ pub fn start( .watch(folder.as_ref(), RecursiveMode::Recursive) .expect("Could not start watcher"); - if let Err(e) = async_watch(AsyncWatchArgs { + async_watch(AsyncWatchArgs { q: consumer, path: folder, show_progress, @@ -342,8 +342,5 @@ pub fn start( snapshot_output, }) .await - { - println!("{e:?}") - } }) }