diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5deaeef1c0..611ad0e66f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,6 +98,9 @@ jobs: - name: Install npm packages run: yarn install + - name: Install testrepo deps + run: cd rewatch/testrepo && yarn install + - name: Install dependencies (Linux) if: runner.os == 'Linux' uses: awalsh128/cache-apt-pkgs-action@v1.4.3 @@ -133,6 +136,8 @@ jobs: - name: Copy rewatch binary run: | cp rewatch/target/${{ matrix.rust-target }}/release/rewatch${{ runner.os == 'Windows' && '.exe' || '' }} rewatch + mkdir -p rewatch/target/release + cp rewatch/target/${{ matrix.rust-target }}/release/rewatch${{ runner.os == 'Windows' && '.exe' || '' }} rewatch/target/release ./scripts/copyExes.js --rewatch shell: bash @@ -354,6 +359,9 @@ jobs: if: runner.os != 'Windows' run: make -C tests/gentype_tests/typescript-react-example clean test + - name: Run rewatch tests + run: make test-rewatch + - name: Run syntax benchmarks if: matrix.benchmarks run: ./_build/install/default/bin/syntax_benchmarks | tee tests/benchmark-output.json @@ -507,7 +515,6 @@ jobs: working-directory: ${{ steps.tmp-dir.outputs.path }} - name: Install ReScript package in rewatch/testrepo - if: runner.os == 'Linux' run: | COMMIT_SHA="${{ github.event.pull_request.head.sha || github.sha }}" yarn add "rescript@https://pkg.pr.new/rescript-lang/rescript@${COMMIT_SHA::7}" @@ -516,8 +523,7 @@ jobs: - name: Run rewatch integration tests # Currently failing on Windows and intermittently on macOS - if: runner.os == 'Linux' - run: make test-rewatch-ci + run: make test-rewatch-integration publish: needs: diff --git a/Makefile b/Makefile index 96a166ce9e..dcc4fc7550 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,7 @@ test-gentype: test-rewatch: bash ./rewatch/tests/suite-ci.sh -test-rewatch-ci: +test-rewatch-integration: bash ./rewatch/tests/suite-ci.sh node_modules/.bin/rewatch test-all: test test-gentype test-analysis test-tools test-rewatch diff --git a/rewatch/src/build.rs b/rewatch/src/build.rs index 15f02de8b9..c76120064e 100644 --- a/rewatch/src/build.rs +++ b/rewatch/src/build.rs @@ -21,7 +21,7 @@ use serde::Serialize; use std::fmt; use std::fs::File; use std::io::{stdout, Write}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::time::{Duration, Instant}; use self::compile::compiler_args; @@ -55,15 +55,14 @@ pub struct CompilerArgs { } pub fn get_compiler_args( - path: &str, + path: &Path, rescript_version: Option, - bsc_path: Option, + bsc_path: &Option, build_dev_deps: bool, ) -> Result { let filename = &helpers::get_abs_path(path); - let package_root = helpers::get_abs_path( - &helpers::get_nearest_config(&std::path::PathBuf::from(path)).expect("Couldn't find package root"), - ); + let package_root = + helpers::get_abs_path(&helpers::get_nearest_config(&path).expect("Couldn't find package root")); let workspace_root = get_workspace_root(&package_root).map(|p| helpers::get_abs_path(&p)); let root_rescript_config = packages::read_config(&workspace_root.to_owned().unwrap_or(package_root.to_owned()))?; @@ -73,17 +72,13 @@ pub fn get_compiler_args( } else { let bsc_path = match bsc_path { Some(bsc_path) => helpers::get_abs_path(&bsc_path), - None => helpers::get_bsc(&package_root, workspace_root.to_owned()), + None => helpers::get_bsc(&package_root, &workspace_root), }; helpers::get_rescript_version(&bsc_path) }; // make PathBuf from package root and get the relative path for filename - let relative_filename = PathBuf::from(&filename) - .strip_prefix(PathBuf::from(&package_root)) - .unwrap() - .to_string_lossy() - .to_string(); + let relative_filename = filename.strip_prefix(PathBuf::from(&package_root)).unwrap(); let file_path = PathBuf::from(&package_root).join(filename); let contents = helpers::read_file(&file_path).expect("Error reading file"); @@ -97,18 +92,18 @@ pub fn get_compiler_args( workspace_root.as_ref().unwrap_or(&package_root), &contents, ); - let is_interface = filename.ends_with('i'); + let is_interface = filename.to_string_lossy().ends_with('i'); let has_interface = if is_interface { true } else { - let mut interface_filename = filename.to_string(); + let mut interface_filename = filename.to_string_lossy().to_string(); interface_filename.push('i'); PathBuf::from(&interface_filename).exists() }; let compiler_args = compiler_args( &rescript_config, &root_rescript_config, - &ast_path.to_string_lossy(), + &ast_path, &rescript_version, &relative_filename, is_interface, @@ -131,20 +126,21 @@ pub fn initialize_build( default_timing: Option, filter: &Option, show_progress: bool, - path: &str, - bsc_path: Option, + path: &Path, + bsc_path: &Option, build_dev_deps: bool, + snapshot_output: bool, ) -> Result { let project_root = helpers::get_abs_path(path); let workspace_root = helpers::get_workspace_root(&project_root); let bsc_path = match bsc_path { Some(bsc_path) => helpers::get_abs_path(&bsc_path), - None => helpers::get_bsc(&project_root, workspace_root.to_owned()), + None => helpers::get_bsc(&project_root, &workspace_root), }; let root_config_name = packages::read_package_name(&project_root)?; let rescript_version = helpers::get_rescript_version(&bsc_path); - if show_progress { + if !snapshot_output && show_progress { print!("{} {}Building package tree...", style("[1/7]").bold().dim(), TREE); let _ = stdout().flush(); } @@ -159,7 +155,7 @@ pub fn initialize_build( )?; let timing_package_tree_elapsed = timing_package_tree.elapsed(); - if show_progress { + if !snapshot_output && show_progress { println!( "{}{} {}Built package tree in {:.2}s", LINE_CLEAR, @@ -177,7 +173,7 @@ pub fn initialize_build( let timing_source_files = Instant::now(); - if show_progress { + if !snapshot_output && show_progress { print!( "{} {}Finding source files...", style("[2/7]").bold().dim(), @@ -197,7 +193,7 @@ pub fn initialize_build( packages::parse_packages(&mut build_state); let timing_source_files_elapsed = timing_source_files.elapsed(); - if show_progress { + if !snapshot_output && show_progress { println!( "{}{} {}Found source files in {:.2}s", LINE_CLEAR, @@ -219,7 +215,7 @@ pub fn initialize_build( let compile_assets_state = read_compile_state::read(&mut build_state); let timing_compile_state_elapsed = timing_compile_state.elapsed(); - if show_progress { + if !snapshot_output && show_progress { println!( "{}{} {}Read compile state {:.2}s", LINE_CLEAR, @@ -241,15 +237,19 @@ pub fn initialize_build( let timing_cleanup_elapsed = timing_cleanup.elapsed(); if show_progress { - println!( - "{}{} {}Cleaned {}/{} {:.2}s", - LINE_CLEAR, - style("[4/7]").bold().dim(), - SWEEP, - diff_cleanup, - total_cleanup, - default_timing.unwrap_or(timing_cleanup_elapsed).as_secs_f64() - ); + if snapshot_output { + println!("Cleaned {}/{}", diff_cleanup, total_cleanup) + } else { + println!( + "{}{} {}Cleaned {}/{} {:.2}s", + LINE_CLEAR, + style("[4/7]").bold().dim(), + SWEEP, + diff_cleanup, + total_cleanup, + default_timing.unwrap_or(timing_cleanup_elapsed).as_secs_f64() + ); + } } Ok(build_state) @@ -260,20 +260,40 @@ fn format_step(current: usize, total: usize) -> console::StyledObject { } #[derive(Debug, Clone)] -pub enum IncrementalBuildError { +pub enum IncrementalBuildErrorKind { SourceFileParseError, CompileError(Option), } +#[derive(Debug, Clone)] +pub struct IncrementalBuildError { + pub snapshot_output: bool, + pub kind: IncrementalBuildErrorKind, +} + impl fmt::Display for IncrementalBuildError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::SourceFileParseError => write!(f, "{} {}Could not parse Source Files", LINE_CLEAR, CROSS,), - Self::CompileError(Some(e)) => { - write!(f, "{} {}Failed to Compile. Error: {e}", LINE_CLEAR, CROSS,) + match &self.kind { + IncrementalBuildErrorKind::SourceFileParseError => { + if self.snapshot_output { + write!(f, "{} Could not parse Source Files", LINE_CLEAR,) + } else { + write!(f, "{} {}Could not parse Source Files", LINE_CLEAR, CROSS,) + } + } + IncrementalBuildErrorKind::CompileError(Some(e)) => { + if self.snapshot_output { + write!(f, "{} Failed to Compile. Error: {e}", LINE_CLEAR,) + } else { + write!(f, "{} {}Failed to Compile. Error: {e}", LINE_CLEAR, CROSS,) + } } - Self::CompileError(None) => { - write!(f, "{} {}Failed to Compile. See Errors Above", LINE_CLEAR, CROSS,) + IncrementalBuildErrorKind::CompileError(None) => { + if self.snapshot_output { + write!(f, "{} Failed to Compile. See Errors Above", LINE_CLEAR,) + } else { + write!(f, "{} {}Failed to Compile. See Errors Above", LINE_CLEAR, CROSS,) + } } } } @@ -287,10 +307,11 @@ pub fn incremental_build( only_incremental: bool, create_sourcedirs: bool, build_dev_deps: bool, + snapshot_output: bool, ) -> Result<(), IncrementalBuildError> { logs::initialize(&build_state.packages); let num_dirty_modules = build_state.modules.values().filter(|m| is_dirty(m)).count() as u64; - let pb = if show_progress { + let pb = if !snapshot_output && show_progress { ProgressBar::new(num_dirty_modules) } else { ProgressBar::hidden() @@ -313,20 +334,25 @@ pub fn incremental_build( match result_asts { Ok(_ast) => { if show_progress { - println!( - "{}{} {}Parsed {} source files in {:.2}s", - LINE_CLEAR, - format_step(current_step, total_steps), - CODE, - num_dirty_modules, - default_timing.unwrap_or(timing_ast_elapsed).as_secs_f64() - ); - pb.finish(); + if snapshot_output { + println!("Parsed {} source files", num_dirty_modules) + } else { + println!( + "{}{} {}Parsed {} source files in {:.2}s", + LINE_CLEAR, + format_step(current_step, total_steps), + CODE, + num_dirty_modules, + default_timing.unwrap_or(timing_ast_elapsed).as_secs_f64() + ); + pb.finish(); + } } } Err(err) => { logs::finalize(&build_state.packages); - if show_progress { + + if !snapshot_output && show_progress { println!( "{}{} {}Error parsing source files in {:.2}s", LINE_CLEAR, @@ -337,8 +363,11 @@ pub fn incremental_build( pb.finish(); } - println!("Could not parse source files: {}", &err); - return Err(IncrementalBuildError::SourceFileParseError); + println!("{}", &err); + return Err(IncrementalBuildError { + kind: IncrementalBuildErrorKind::SourceFileParseError, + snapshot_output, + }); } } let timing_deps = Instant::now(); @@ -346,7 +375,7 @@ pub fn incremental_build( let timing_deps_elapsed = timing_deps.elapsed(); current_step += 1; - if show_progress { + if !snapshot_output && show_progress { println!( "{}{} {}Collected deps in {:.2}s", LINE_CLEAR, @@ -370,7 +399,7 @@ pub fn incremental_build( }; let start_compiling = Instant::now(); - let pb = if show_progress { + let pb = if !snapshot_output && show_progress { ProgressBar::new(build_state.modules.len().try_into().unwrap()) } else { ProgressBar::hidden() @@ -391,7 +420,10 @@ pub fn incremental_build( |size| pb.set_length(size), build_dev_deps, ) - .map_err(|e| IncrementalBuildError::CompileError(Some(e.to_string())))?; + .map_err(|e| IncrementalBuildError { + kind: IncrementalBuildErrorKind::CompileError(Some(e.to_string())), + snapshot_output, + })?; let compile_duration = start_compiling.elapsed(); @@ -402,14 +434,18 @@ pub fn incremental_build( pb.finish(); if !compile_errors.is_empty() { if show_progress { - println!( - "{}{} {}Compiled {} modules in {:.2}s", - LINE_CLEAR, - format_step(current_step, total_steps), - CROSS, - num_compiled_modules, - default_timing.unwrap_or(compile_duration).as_secs_f64() - ); + if snapshot_output { + println!("Compiled {} modules", num_compiled_modules) + } else { + println!( + "{}{} {}Compiled {} modules in {:.2}s", + LINE_CLEAR, + format_step(current_step, total_steps), + CROSS, + num_compiled_modules, + default_timing.unwrap_or(compile_duration).as_secs_f64() + ); + } } if helpers::contains_ascii_characters(&compile_warnings) { println!("{}", &compile_warnings); @@ -417,17 +453,24 @@ pub fn incremental_build( if helpers::contains_ascii_characters(&compile_errors) { println!("{}", &compile_errors); } - Err(IncrementalBuildError::CompileError(None)) + Err(IncrementalBuildError { + kind: IncrementalBuildErrorKind::CompileError(None), + snapshot_output, + }) } else { if show_progress { - println!( - "{}{} {}Compiled {} modules in {:.2}s", - LINE_CLEAR, - format_step(current_step, total_steps), - SWORDS, - num_compiled_modules, - default_timing.unwrap_or(compile_duration).as_secs_f64() - ); + if snapshot_output { + println!("Compiled {} modules", num_compiled_modules) + } else { + println!( + "{}{} {}Compiled {} modules in {:.2}s", + LINE_CLEAR, + format_step(current_step, total_steps), + SWORDS, + num_compiled_modules, + default_timing.unwrap_or(compile_duration).as_secs_f64() + ); + } } if helpers::contains_ascii_characters(&compile_warnings) { @@ -445,20 +488,20 @@ pub fn incremental_build( pub fn write_build_ninja(build_state: &BuildState) { for package in build_state.packages.values() { // write empty file: - let mut f = File::create(std::path::Path::new(&package.get_build_path()).join("build.ninja")) - .expect("Unable to write file"); + let mut f = File::create(package.get_build_path().join("build.ninja")).expect("Unable to write file"); f.write_all(b"").expect("unable to write to ninja file"); } } pub fn build( filter: &Option, - path: &str, + path: &Path, show_progress: bool, no_timing: bool, create_sourcedirs: bool, - bsc_path: Option, + bsc_path: &Option, build_dev_deps: bool, + snapshot_output: bool, ) -> Result { let default_timing: Option = if no_timing { Some(std::time::Duration::new(0.0 as u64, 0.0 as u32)) @@ -473,6 +516,7 @@ pub fn build( path, bsc_path, build_dev_deps, + snapshot_output, ) .map_err(|e| anyhow!("Could not initialize build. Error: {e}"))?; @@ -484,9 +528,10 @@ pub fn build( false, create_sourcedirs, build_dev_deps, + snapshot_output, ) { Ok(_) => { - if show_progress { + if !snapshot_output && show_progress { let timing_total_elapsed = timing_total.elapsed(); println!( "\n{}{}Finished Compilation in {:.2}s", diff --git a/rewatch/src/build/build_types.rs b/rewatch/src/build/build_types.rs index 51700e7d7c..956cc5e159 100644 --- a/rewatch/src/build/build_types.rs +++ b/rewatch/src/build/build_types.rs @@ -1,6 +1,6 @@ use crate::build::packages::{Namespace, Package}; use ahash::{AHashMap, AHashSet}; -use std::{fmt::Display, time::SystemTime}; +use std::{fmt::Display, path::PathBuf, time::SystemTime}; #[derive(Debug, Clone, PartialEq)] pub enum ParseState { @@ -19,7 +19,7 @@ pub enum CompileState { } #[derive(Debug, Clone, PartialEq)] pub struct Interface { - pub path: String, + pub path: PathBuf, pub parse_state: ParseState, pub compile_state: CompileState, pub last_modified: SystemTime, @@ -28,7 +28,7 @@ pub struct Interface { #[derive(Debug, Clone, PartialEq)] pub struct Implementation { - pub path: String, + pub path: PathBuf, pub parse_state: ParseState, pub compile_state: CompileState, pub last_modified: SystemTime, @@ -91,12 +91,12 @@ pub struct BuildState { pub modules: AHashMap, pub packages: AHashMap, pub module_names: AHashSet, - pub project_root: String, + pub project_root: PathBuf, pub root_config_name: String, pub deleted_modules: AHashSet, pub rescript_version: String, - pub bsc_path: String, - pub workspace_root: Option, + pub bsc_path: PathBuf, + pub workspace_root: Option, pub deps_initialized: bool, } @@ -109,12 +109,12 @@ impl BuildState { self.modules.get(module_name) } pub fn new( - project_root: String, + project_root: PathBuf, root_config_name: String, packages: AHashMap, - workspace_root: Option, + workspace_root: Option, rescript_version: String, - bsc_path: String, + bsc_path: PathBuf, ) -> Self { Self { module_names: AHashSet::new(), @@ -136,20 +136,22 @@ impl BuildState { } } +#[derive(Debug)] pub struct AstModule { pub module_name: String, pub package_name: String, pub namespace: Namespace, pub last_modified: SystemTime, - pub ast_file_path: String, + pub ast_file_path: PathBuf, pub is_root: bool, pub suffix: String, } +#[derive(Debug)] pub struct CompileAssetsState { - pub ast_modules: AHashMap, + pub ast_modules: AHashMap, pub cmi_modules: AHashMap, pub cmt_modules: AHashMap, - pub ast_rescript_file_locations: AHashSet, - pub rescript_file_locations: AHashSet, + pub ast_rescript_file_locations: AHashSet, + pub rescript_file_locations: AHashSet, } diff --git a/rewatch/src/build/clean.rs b/rewatch/src/build/clean.rs index ba53e4ff5e..5624e6ac33 100644 --- a/rewatch/src/build/clean.rs +++ b/rewatch/src/build/clean.rs @@ -7,9 +7,10 @@ use anyhow::Result; use console::style; use rayon::prelude::*; use std::io::Write; +use std::path::{Path, PathBuf}; use std::time::Instant; -fn remove_ast(package: &packages::Package, source_file: &str) { +fn remove_ast(package: &packages::Package, source_file: &Path) { let _ = std::fs::remove_file(helpers::get_compiler_asset( package, &packages::Namespace::NoNamespace, @@ -18,7 +19,7 @@ fn remove_ast(package: &packages::Package, source_file: &str) { )); } -fn remove_iast(package: &packages::Package, source_file: &str) { +fn remove_iast(package: &packages::Package, source_file: &Path) { let _ = std::fs::remove_file(helpers::get_compiler_asset( package, &packages::Namespace::NoNamespace, @@ -27,15 +28,14 @@ fn remove_iast(package: &packages::Package, source_file: &str) { )); } -fn remove_mjs_file(source_file: &str, suffix: &String) { - let _ = std::fs::remove_file(helpers::change_extension( - source_file, +fn remove_mjs_file(source_file: &Path, suffix: &String) { + let _ = std::fs::remove_file(source_file.with_extension( // suffix.to_string includes the ., so we need to remove it &suffix.to_string()[1..], )); } -fn remove_compile_asset(package: &packages::Package, source_file: &str, extension: &str) { +fn remove_compile_asset(package: &packages::Package, source_file: &Path, extension: &str) { let _ = std::fs::remove_file(helpers::get_compiler_asset( package, &package.namespace, @@ -50,7 +50,7 @@ fn remove_compile_asset(package: &packages::Package, source_file: &str, extensio )); } -pub fn remove_compile_assets(package: &packages::Package, source_file: &str) { +pub fn remove_compile_assets(package: &packages::Package, source_file: &Path) { // optimization // only issue cmti if there is an interfacce file for extension in &["cmj", "cmi", "cmt", "cmti"] { @@ -79,23 +79,20 @@ pub fn clean_mjs_files(build_state: &BuildState) { .filter_map(|spec| { if spec.in_source { Some(( - std::path::PathBuf::from(package.path.to_string()) - .join(&source_file.implementation.path) - .to_string_lossy() - .to_string(), + package.path.join(&source_file.implementation.path), root_package.config.get_suffix(spec), )) } else { None } }) - .collect::>(), + .collect::>(), ) } _ => None, }) .flatten() - .collect::>(); + .collect::>(); rescript_file_locations .par_iter() @@ -118,7 +115,7 @@ pub fn cleanup_previous_build( let diff = compile_assets_state .ast_rescript_file_locations .difference(&compile_assets_state.rescript_file_locations) - .collect::>(); + .collect::>(); let diff_len = diff.len(); @@ -133,7 +130,7 @@ pub fn cleanup_previous_build( .. } = compile_assets_state .ast_modules - .get(&res_file_location.to_string()) + .get(*res_file_location) .expect("Could not find module name for ast file"); let package = build_state @@ -173,18 +170,14 @@ pub fn cleanup_previous_build( .get_mut(module_name) .expect("Could not find module for ast file"); - let compile_dirty = compile_assets_state.cmt_modules.get(module_name); + let cmt_last_modified = compile_assets_state.cmt_modules.get(module_name); // if there is a new AST but it has not been compiled yet, we mark the module as compile dirty // we do this by checking if the cmt file is newer than the AST file. We always compile the // interface AND implementation. For some reason the CMI file is not always rewritten if it // doesn't have any changes, that's why we just look at the CMT file. - if let Some(compile_dirty) = compile_dirty { - let last_modified = Some(ast_last_modified); - - if let Some(last_modified) = last_modified { - if compile_dirty > last_modified && !deleted_interfaces.contains(module_name) { - module.compile_dirty = false; - } + if let Some(cmt_last_modified) = cmt_last_modified { + if cmt_last_modified > ast_last_modified && !deleted_interfaces.contains(module_name) { + module.compile_dirty = false; } } @@ -338,7 +331,13 @@ pub fn cleanup_after_build(build_state: &BuildState) { }); } -pub fn clean(path: &str, show_progress: bool, bsc_path: Option, build_dev_deps: bool) -> Result<()> { +pub fn clean( + path: &Path, + show_progress: bool, + bsc_path: &Option, + build_dev_deps: bool, + snapshot_output: bool, +) -> Result<()> { let project_root = helpers::get_abs_path(path); let workspace_root = helpers::get_workspace_root(&project_root); let packages = packages::make( @@ -352,13 +351,13 @@ pub fn clean(path: &str, show_progress: bool, bsc_path: Option, build_de let root_config_name = packages::read_package_name(&project_root)?; let bsc_path = match bsc_path { Some(bsc_path) => helpers::get_abs_path(&bsc_path), - None => helpers::get_bsc(&project_root, workspace_root.to_owned()), + None => helpers::get_bsc(&project_root, &workspace_root), }; let rescript_version = helpers::get_rescript_version(&bsc_path); let timing_clean_compiler_assets = Instant::now(); - if show_progress { + if !snapshot_output && show_progress { print!( "{} {}Cleaning compiler assets...", style("[1/2]").bold().dim(), @@ -368,13 +367,17 @@ pub fn clean(path: &str, show_progress: bool, bsc_path: Option, build_de }; packages.iter().for_each(|(_, package)| { if show_progress { - print!( - "{}{} {}Cleaning {}...", - LINE_CLEAR, - style("[1/2]").bold().dim(), - SWEEP, - package.name - ); + if snapshot_output { + println!("Cleaning {}", package.name) + } else { + print!( + "{}{} {}Cleaning {}...", + LINE_CLEAR, + style("[1/2]").bold().dim(), + SWEEP, + package.name + ); + } let _ = std::io::stdout().flush(); } @@ -388,7 +391,7 @@ pub fn clean(path: &str, show_progress: bool, bsc_path: Option, build_de }); let timing_clean_compiler_assets_elapsed = timing_clean_compiler_assets.elapsed(); - if show_progress { + if !snapshot_output && show_progress { println!( "{}{} {}Cleaned compiler assets in {:.2}s", LINE_CLEAR, @@ -400,7 +403,7 @@ pub fn clean(path: &str, show_progress: bool, bsc_path: Option, build_de } let timing_clean_mjs = Instant::now(); - if show_progress { + if !snapshot_output && show_progress { println!("{} {}Cleaning mjs files...", style("[2/2]").bold().dim(), SWEEP); let _ = std::io::stdout().flush(); } @@ -416,7 +419,7 @@ pub fn clean(path: &str, show_progress: bool, bsc_path: Option, build_de clean_mjs_files(&build_state); let timing_clean_mjs_elapsed = timing_clean_mjs.elapsed(); - if show_progress { + if !snapshot_output && show_progress { println!( "{}{} {}Cleaned mjs files in {:.2}s", LINE_CLEAR, diff --git a/rewatch/src/build/compile.rs b/rewatch/src/build/compile.rs index 73260ec6a9..dac7cac8a8 100644 --- a/rewatch/src/build/compile.rs +++ b/rewatch/src/build/compile.rs @@ -7,12 +7,13 @@ use super::logs; use super::packages; use crate::config; use crate::helpers; +use crate::helpers::StrippedVerbatimPath; use ahash::{AHashMap, AHashSet}; use anyhow::anyhow; use console::style; use log::{debug, trace}; use rayon::prelude::*; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::Command; use std::time::SystemTime; @@ -162,7 +163,7 @@ pub fn compile( let result = compile_file( package, root_package, - &helpers::get_ast_path(&path).to_string_lossy(), + &helpers::get_ast_path(&path), module, &build_state.rescript_version, true, @@ -179,7 +180,7 @@ pub fn compile( let result = compile_file( package, root_package, - &helpers::get_ast_path(&source_file.implementation.path).to_string_lossy(), + &helpers::get_ast_path(&source_file.implementation.path), module, &build_state.rescript_version, false, @@ -352,13 +353,13 @@ pub fn compile( pub fn compiler_args( config: &config::Config, root_config: &config::Config, - ast_path: &str, + ast_path: &Path, version: &str, - file_path: &str, + file_path: &Path, is_interface: bool, has_interface: bool, - project_root: &str, - workspace_root: &Option, + project_root: &Path, + workspace_root: &Option, // if packages are known, we pass a reference here // this saves us a scan to find their paths packages: &Option<&AHashMap>, @@ -448,22 +449,16 @@ pub fn compiler_args( "{}:{}:{}", spec.module, if spec.in_source { - Path::new(file_path) - .parent() - .unwrap() - .to_str() - .unwrap() - .to_string() + file_path.parent().unwrap().to_str().unwrap().to_string() } else { - format!( - "lib/{}", - Path::join( + Path::new("lib") + .join(Path::join( Path::new(&spec.get_out_of_source_dir()), - Path::new(file_path).parent().unwrap() - ) + file_path.parent().unwrap(), + )) .to_str() .unwrap() - ) + .to_string() }, root_config.get_suffix(spec), ), @@ -476,7 +471,10 @@ pub fn compiler_args( vec![ namespace_args, read_cmi_args, - vec!["-I".to_string(), "../ocaml".to_string()], + vec![ + "-I".to_string(), + Path::new("..").join("ocaml").to_string_lossy().to_string(), + ], dependency_paths.concat(), uncurried_args, bsc_flags.to_owned(), @@ -494,7 +492,7 @@ pub fn compiler_args( // abs_node_modules_path.to_string() + "/rescript/ocaml", // ], vec!["-bs-v".to_string(), format!("{}", version)], - vec![ast_path.to_string()], + vec![ast_path.to_string_lossy().to_string()], ] .concat() } @@ -523,8 +521,8 @@ impl DependentPackage { fn get_dependency_paths( config: &config::Config, - project_root: &str, - workspace_root: &Option, + project_root: &Path, + workspace_root: &Option, packages: &Option<&AHashMap>, build_dev_deps: bool, ) -> Vec> { @@ -553,14 +551,19 @@ fn get_dependency_paths( .filter_map(|dependent_package| { let package_name = dependent_package.name(); let dependency_path = if let Some(packages) = packages { - packages.get(package_name).map(|package| package.path.to_string()) + packages + .get(package_name) + .as_ref() + .map(|package| package.path.clone()) } else { packages::read_dependency(package_name, project_root, project_root, workspace_root).ok() } .map(|canonicalized_path| { vec![ "-I".to_string(), - packages::get_ocaml_build_path(&canonicalized_path), + packages::get_ocaml_build_path(&canonicalized_path) + .to_string_lossy() + .to_string(), ] }); @@ -579,14 +582,14 @@ fn get_dependency_paths( fn compile_file( package: &packages::Package, root_package: &packages::Package, - ast_path: &str, + ast_path: &Path, module: &Module, version: &str, is_interface: bool, - bsc_path: &str, + bsc_path: &Path, packages: &AHashMap, - project_root: &str, - workspace_root: &Option, + project_root: &Path, + workspace_root: &Option, build_dev_deps: bool, ) -> Result, String> { let ocaml_build_path_abs = package.get_ocaml_build_path(); @@ -595,7 +598,8 @@ fn compile_file( SourceType::SourceFile(ref source_file) => Ok(&source_file.implementation.path), sourcetype => Err(format!( "Tried to compile a file that is not a source file ({}). Path to AST: {}. ", - sourcetype, ast_path + sourcetype, + ast_path.to_string_lossy() )), }?; let module_name = helpers::file_path_to_module_name(implementation_file_path, &package.namespace); @@ -615,7 +619,13 @@ fn compile_file( ); let to_mjs = Command::new(bsc_path) - .current_dir(helpers::canonicalize_string_path(&build_path_abs.to_owned()).unwrap()) + .current_dir( + build_path_abs + .canonicalize() + .map(StrippedVerbatimPath::to_stripped_verbatim_path) + .ok() + .unwrap(), + ) .args(to_mjs_args) .output(); @@ -639,41 +649,46 @@ fn compile_file( // perhaps we can do this copying somewhere else if !is_interface { let _ = std::fs::copy( - std::path::Path::new(&package.get_build_path()) + package + .get_build_path() .join(dir) // because editor tooling doesn't support namespace entries yet // we just remove the @ for now. This makes sure the editor support // doesn't break - .join(module_name.to_owned() + ".cmi"), - ocaml_build_path_abs.to_string() + "/" + &module_name + ".cmi", + .join(format!("{}.cmi", module_name)), + ocaml_build_path_abs.join(format!("{}.cmi", module_name)), ); let _ = std::fs::copy( - std::path::Path::new(&package.get_build_path()) + package + .get_build_path() .join(dir) - .join(module_name.to_owned() + ".cmj"), - ocaml_build_path_abs.to_string() + "/" + &module_name + ".cmj", + .join(format!("{}.cmj", module_name)), + ocaml_build_path_abs.join(format!("{}.cmj", module_name)), ); let _ = std::fs::copy( - std::path::Path::new(&package.get_build_path()) + package + .get_build_path() .join(dir) // because editor tooling doesn't support namespace entries yet // we just remove the @ for now. This makes sure the editor support // doesn't break - .join(module_name.to_owned() + ".cmt"), - ocaml_build_path_abs.to_string() + "/" + &module_name + ".cmt", + .join(format!("{}.cmt", module_name)), + ocaml_build_path_abs.join(format!("{}.cmt", module_name)), ); } else { let _ = std::fs::copy( - std::path::Path::new(&package.get_build_path()) + package + .get_build_path() .join(dir) - .join(module_name.to_owned() + ".cmti"), - ocaml_build_path_abs.to_string() + "/" + &module_name + ".cmti", + .join(format!("{}.cmti", module_name)), + ocaml_build_path_abs.join(format!("{}.cmti", module_name)), ); let _ = std::fs::copy( - std::path::Path::new(&package.get_build_path()) + package + .get_build_path() .join(dir) - .join(module_name.to_owned() + ".cmi"), - ocaml_build_path_abs.to_string() + "/" + &module_name + ".cmi", + .join(format!("{}.cmi", module_name)), + ocaml_build_path_abs.join(format!("{}.cmi", module_name)), ); } @@ -686,14 +701,15 @@ fn compile_file( // editor tools expects the source file in lib/bs for finding the current package // and in lib/ocaml when referencing modules in other packages let _ = std::fs::copy( - std::path::Path::new(&package.path).join(path), - std::path::Path::new(&package.get_build_path()).join(path), + Path::new(&package.path).join(path), + package.get_build_path().join(path), ) .expect("copying source file failed"); let _ = std::fs::copy( - std::path::Path::new(&package.path).join(path), - std::path::Path::new(&package.get_ocaml_build_path()) + Path::new(&package.path).join(path), + package + .get_ocaml_build_path() .join(std::path::Path::new(path).file_name().unwrap()), ) .expect("copying source file failed"); @@ -709,14 +725,15 @@ fn compile_file( // editor tools expects the source file in lib/bs for finding the current package // and in lib/ocaml when referencing modules in other packages let _ = std::fs::copy( - std::path::Path::new(&package.path).join(path), - std::path::Path::new(&package.get_build_path()).join(path), + Path::new(&package.path).join(path), + package.get_build_path().join(path), ) .expect("copying source file failed"); let _ = std::fs::copy( - std::path::Path::new(&package.path).join(path), - std::path::Path::new(&package.get_ocaml_build_path()) + Path::new(&package.path).join(path), + package + .get_ocaml_build_path() .join(std::path::Path::new(path).file_name().unwrap()), ) .expect("copying source file failed"); @@ -733,11 +750,11 @@ fn compile_file( .. }) => { let source = helpers::get_source_file_from_rescript_file( - &std::path::Path::new(&package.path).join(path), + &Path::new(&package.path).join(path), &root_package.config.get_suffix(spec), ); let destination = helpers::get_source_file_from_rescript_file( - &std::path::Path::new(&package.get_build_path()).join(path), + &package.get_build_path().join(path), &root_package.config.get_suffix(spec), ); diff --git a/rewatch/src/build/deps.rs b/rewatch/src/build/deps.rs index 59ddd3c2a7..373f449d4a 100644 --- a/rewatch/src/build/deps.rs +++ b/rewatch/src/build/deps.rs @@ -12,8 +12,8 @@ fn get_dep_modules( package: &packages::Package, ) -> AHashSet { let mut deps = AHashSet::new(); - let ast_file = package.get_build_path() + "/" + ast_file; - if let Ok(lines) = helpers::read_lines(ast_file.to_string()) { + let ast_file = package.get_build_path().join(ast_file); + if let Ok(lines) = helpers::read_lines(&ast_file) { // we skip the first line with is some null characters // the following lines in the AST are the dependency modules // we stop when we hit a line that starts with a "/", this is the path of the file. @@ -27,7 +27,7 @@ fn get_dep_modules( } } } else { - panic!("Could not read file {}", ast_file); + panic!("Could not read file {}", ast_file.to_string_lossy()); } return deps diff --git a/rewatch/src/build/logs.rs b/rewatch/src/build/logs.rs index 5f3b8b4026..0f92896ac3 100644 --- a/rewatch/src/build/logs.rs +++ b/rewatch/src/build/logs.rs @@ -6,6 +6,7 @@ use rayon::prelude::*; use regex::Regex; use std::fs::File; use std::io::prelude::*; +use std::path::PathBuf; use super::packages; @@ -14,13 +15,13 @@ enum Location { Ocaml, } -fn get_log_file_path(package: &packages::Package, subfolder: Location) -> String { +fn get_log_file_path(package: &packages::Package, subfolder: Location) -> PathBuf { let build_folder = match subfolder { Location::Bs => package.get_build_path(), Location::Ocaml => package.get_ocaml_build_path(), }; - build_folder.to_owned() + "/.compiler.log" + build_folder.join(".compiler.log") } fn escape_colours(str: &str) -> String { @@ -59,7 +60,10 @@ pub fn append(package: &packages::Package, str: &str) { .append(true) .open(get_log_file_path(package, Location::Bs)) .map(|file| write_to_log_file(file, &package.name, str)) - .expect(&("Cannot write compilerlog: ".to_owned() + &get_log_file_path(package, Location::Bs))); + .expect( + &("Cannot write compilerlog: ".to_owned() + + &get_log_file_path(package, Location::Bs).to_string_lossy()), + ); } pub fn finalize(packages: &AHashMap) { diff --git a/rewatch/src/build/namespaces.rs b/rewatch/src/build/namespaces.rs index 39b943d848..c5d8355e28 100644 --- a/rewatch/src/build/namespaces.rs +++ b/rewatch/src/build/namespaces.rs @@ -1,8 +1,10 @@ use crate::build::packages; -use crate::helpers; +use crate::helpers::StrippedVerbatimPath; use ahash::AHashSet; use std::fs::File; use std::io::Write; +use std::path::Path; +use std::path::PathBuf; use std::process::Command; // Namespaces work like the following: The build system will generate a file @@ -23,13 +25,13 @@ pub fn gen_mlmap( package: &packages::Package, namespace: &str, depending_modules: &AHashSet, -) -> String { +) -> PathBuf { let build_path_abs = package.get_build_path(); // we don't really need to create a digest, because we track if we need to // recompile in a different way but we need to put it in the file for it to // be readable. - let path = build_path_abs.to_string() + "/" + namespace + ".mlmap"; + let path = build_path_abs.join(format!("{}.mlmap", namespace)); let mut file = File::create(&path).expect("Unable to create mlmap"); file.write_all(b"randjbuildsystem\n") @@ -46,16 +48,22 @@ pub fn gen_mlmap( file.write_all(b"\n").unwrap(); } - path.to_string() + path } -pub fn compile_mlmap(package: &packages::Package, namespace: &str, bsc_path: &str) { +pub fn compile_mlmap(package: &packages::Package, namespace: &str, bsc_path: &Path) { let build_path_abs = package.get_build_path(); let mlmap_name = format!("{}.mlmap", namespace); let args = vec!["-w", "-49", "-color", "always", "-no-alias-deps", &mlmap_name]; let _ = Command::new(bsc_path) - .current_dir(helpers::canonicalize_string_path(&build_path_abs).unwrap()) + .current_dir( + build_path_abs + .canonicalize() + .map(StrippedVerbatimPath::to_stripped_verbatim_path) + .ok() + .unwrap(), + ) .args(args) .output() .expect("err"); diff --git a/rewatch/src/build/packages.rs b/rewatch/src/build/packages.rs index d85f93f754..f3a5871b1f 100644 --- a/rewatch/src/build/packages.rs +++ b/rewatch/src/build/packages.rs @@ -4,6 +4,7 @@ use super::packages; use crate::config; use crate::helpers; use crate::helpers::emojis::*; +use crate::helpers::StrippedVerbatimPath; use ahash::{AHashMap, AHashSet}; use anyhow::{anyhow, Result}; use console::style; @@ -41,7 +42,7 @@ impl Namespace { struct Dependency { name: String, config: config::Config, - path: String, + path: PathBuf, is_pinned: bool, dependencies: Vec, } @@ -52,68 +53,64 @@ pub struct Package { pub config: config::Config, pub source_folders: AHashSet, // these are the relative file paths (relative to the package root) - pub source_files: Option>, + pub source_files: Option>, pub namespace: Namespace, pub modules: Option>, // canonicalized dir of the package - pub path: String, + pub path: PathBuf, pub dirs: Option>, pub is_pinned_dep: bool, pub is_local_dep: bool, pub is_root: bool, } -pub fn get_build_path(canonical_path: &str) -> String { - format!("{}/lib/bs", canonical_path) +pub fn get_build_path(canonical_path: &Path) -> PathBuf { + canonical_path.join("lib").join("bs") } -pub fn get_js_path(canonical_path: &str) -> String { - format!("{}/lib/js", canonical_path) +pub fn get_js_path(canonical_path: &Path) -> PathBuf { + canonical_path.join("lib").join("js") } -pub fn get_es6_path(canonical_path: &str) -> String { - format!("{}/lib/es6", canonical_path) +pub fn get_es6_path(canonical_path: &Path) -> PathBuf { + canonical_path.join("lib").join("es6") } -pub fn get_ocaml_build_path(canonical_path: &str) -> String { - format!("{}/lib/ocaml", canonical_path) +pub fn get_ocaml_build_path(canonical_path: &Path) -> PathBuf { + canonical_path.join("lib").join("ocaml") } impl Package { - pub fn get_ocaml_build_path(&self) -> String { + pub fn get_ocaml_build_path(&self) -> PathBuf { get_ocaml_build_path(&self.path) } - pub fn get_build_path(&self) -> String { + pub fn get_build_path(&self) -> PathBuf { get_build_path(&self.path) } - pub fn get_js_path(&self) -> String { + pub fn get_js_path(&self) -> PathBuf { get_js_path(&self.path) } - pub fn get_es6_path(&self) -> String { + pub fn get_es6_path(&self) -> PathBuf { get_es6_path(&self.path) } - pub fn get_mlmap_path(&self) -> String { - self.get_build_path() - + "/" - + &self - .namespace - .to_suffix() - .expect("namespace should be set for mlmap module") - + ".mlmap" + pub fn get_mlmap_path(&self) -> PathBuf { + let suffix = self + .namespace + .to_suffix() + .expect("namespace should be set for mlmap module"); + self.get_build_path().join(format!("{}.mlmap", suffix)) } - pub fn get_mlmap_compile_path(&self) -> String { - self.get_build_path() - + "/" - + &self - .namespace - .to_suffix() - .expect("namespace should be set for mlmap module") - + ".cmi" + pub fn get_mlmap_compile_path(&self) -> PathBuf { + let suffix = self + .namespace + .to_suffix() + .expect("namespace should be set for mlmap module"); + self.get_build_path().join(format!("{}.cmi", suffix)) } } @@ -141,8 +138,8 @@ pub fn read_folders( package_dir: &Path, path: &Path, recurse: bool, -) -> Result, Box> { - let mut map: AHashMap = AHashMap::new(); +) -> Result, Box> { + let mut map: AHashMap = AHashMap::new(); let path_buf = PathBuf::from(path); let meta = fs::metadata(package_dir.join(path)); let path_with_meta = meta.map(|meta| { @@ -174,7 +171,7 @@ pub fn read_folders( let mut path = path.to_owned(); path.push(&name); map.insert( - path.to_string_lossy().to_string(), + path, SourceFileMeta { modified: metadata.modified().unwrap(), }, @@ -230,29 +227,23 @@ fn get_source_dirs(source: config::Source, sub_path: Option) -> AHashSe source_folders } -pub fn read_config(package_dir: &str) -> Result { - let prefix = if package_dir.is_empty() { - "".to_string() - } else { - package_dir.to_string() + "/" - }; - - let rescript_json_path = prefix.to_string() + "rescript.json"; - let bsconfig_json_path = prefix.to_string() + "bsconfig.json"; +pub fn read_config(package_dir: &Path) -> Result { + let rescript_json_path = package_dir.join("rescript.json"); + let bsconfig_json_path = package_dir.join("bsconfig.json"); if Path::new(&rescript_json_path).exists() { - config::read(rescript_json_path) + config::read(&rescript_json_path) } else { - config::read(bsconfig_json_path) + config::read(&bsconfig_json_path) } } pub fn read_dependency( package_name: &str, - parent_path: &str, - project_root: &str, - workspace_root: &Option, -) -> Result { + parent_path: &Path, + project_root: &Path, + workspace_root: &Option, +) -> Result { let path_from_parent = PathBuf::from(helpers::package_path(parent_path, package_name)); let path_from_project_root = PathBuf::from(helpers::package_path(project_root, package_name)); let maybe_path_from_workspace_root = workspace_root @@ -275,8 +266,8 @@ pub fn read_dependency( )), }?; - let canonical_path = match path.canonicalize() { - Ok(canonical_path) => Ok(canonical_path.to_string_lossy().to_string()), + let canonical_path = match path.canonicalize().map(StrippedVerbatimPath::to_stripped_verbatim_path) { + Ok(canonical_path) => Ok(canonical_path), Err(e) => { Err(format!( "Failed canonicalizing the package \"{}\" path \"{}\" (are node_modules up-to-date?)...\nMore details: {}", @@ -301,9 +292,9 @@ pub fn read_dependency( fn read_dependencies( registered_dependencies_set: &mut AHashSet, parent_config: &config::Config, - parent_path: &str, - project_root: &str, - workspace_root: Option, + parent_path: &Path, + project_root: &Path, + workspace_root: &Option, show_progress: bool, ) -> Vec { return parent_config @@ -335,8 +326,9 @@ fn read_dependencies( ); } + let parent_path_str = parent_path.to_string_lossy(); log::error!( - "We could not build package tree reading depencency '{package_name}', at path '{parent_path}'. Error: {error}", + "We could not build package tree reading depencency '{package_name}', at path '{parent_path_str}'. Error: {error}", ); std::process::exit(2) @@ -345,8 +337,9 @@ fn read_dependencies( match read_config(&canonical_path) { Ok(config) => (config, canonical_path), Err(error) => { + let parent_path_str = parent_path.to_string_lossy(); log::error!( - "We could not build package tree '{package_name}', at path '{parent_path}', Error: {error}", + "We could not build package tree '{package_name}', at path '{parent_path_str}'. Error: {error}", ); std::process::exit(2) } @@ -365,7 +358,7 @@ fn read_dependencies( &config, &canonical_path, project_root, - workspace_root.to_owned(), + workspace_root, show_progress ); @@ -390,12 +383,8 @@ fn flatten_dependencies(dependencies: Vec) -> Vec { flattened } -pub fn read_package_name(package_dir: &str) -> Result { - let package_json_path = if package_dir.is_empty() { - "package.json".to_string() - } else { - format!("{}/package.json", package_dir) - }; +pub fn read_package_name(package_dir: &Path) -> Result { + let package_json_path = package_dir.join("package.json"); let package_json_contents = fs::read_to_string(&package_json_path).map_err(|e| anyhow!("Could not read package.json: {}", e))?; @@ -409,7 +398,7 @@ pub fn read_package_name(package_dir: &str) -> Result { .ok_or_else(|| anyhow!("No name field found in package.json")) } -fn make_package(config: config::Config, package_path: &str, is_pinned_dep: bool, is_root: bool) -> Package { +fn make_package(config: config::Config, package_path: &Path, is_pinned_dep: bool, is_root: bool) -> Package { let source_folders = match config.sources.to_owned() { Some(config::OneOrMore::Single(source)) => get_source_dirs(source, None), Some(config::OneOrMore::Multiple(sources)) => { @@ -424,7 +413,8 @@ fn make_package(config: config::Config, package_path: &str, is_pinned_dep: bool, } None => { if !is_root { - log::warn!("Package '{}' has not defined any sources, but is not the root package. This is likely a mistake. It is located: {}", config.name, package_path); + let package_path_str = package_path.to_string_lossy(); + log::warn!("Package '{}' has not defined any sources, but is not the root package. This is likely a mistake. It is located: {}", config.name, package_path_str); } AHashSet::new() @@ -440,21 +430,20 @@ fn make_package(config: config::Config, package_path: &str, is_pinned_dep: bool, namespace: config.get_namespace(), modules: None, // we canonicalize the path name so it's always the same - path: PathBuf::from(package_path) + path: package_path .canonicalize() - .expect("Could not canonicalize") - .to_string_lossy() - .to_string(), + .map(StrippedVerbatimPath::to_stripped_verbatim_path) + .expect("Could not canonicalize"), dirs: None, is_pinned_dep, - is_local_dep: !package_path.contains("node_modules"), + is_local_dep: !package_path.components().any(|c| c.as_os_str() == "node_modules"), is_root, } } fn read_packages( - project_root: &str, - workspace_root: Option, + project_root: &Path, + workspace_root: &Option, show_progress: bool, ) -> Result> { let root_config = read_config(project_root)?; @@ -497,8 +486,8 @@ pub fn get_source_files( filter: &Option, source: &config::PackageSource, build_dev_deps: bool, -) -> AHashMap { - let mut map: AHashMap = AHashMap::new(); +) -> AHashMap { + let mut map: AHashMap = AHashMap::new(); let (recurse, type_) = match source { config::PackageSource { @@ -535,7 +524,7 @@ fn extend_with_children( build_dev_deps: bool, ) -> AHashMap { for (_key, package) in build.iter_mut() { - let mut map: AHashMap = AHashMap::new(); + let mut map: AHashMap = AHashMap::new(); package .source_folders .par_iter() @@ -548,7 +537,7 @@ fn extend_with_children( build_dev_deps, ) }) - .collect::>>() + .collect::>>() .into_iter() .for_each(|source| map.extend(source)); @@ -586,12 +575,12 @@ fn extend_with_children( /// The two step process is there to reduce IO overhead pub fn make( filter: &Option, - root_folder: &str, - workspace_root: &Option, + root_folder: &Path, + workspace_root: &Option, show_progress: bool, build_dev_deps: bool, ) -> Result> { - let map = read_packages(root_folder, workspace_root.to_owned(), show_progress)?; + let map = read_packages(root_folder, workspace_root, show_progress)?; /* Once we have the deduplicated packages, we can add the source files for each - to minimize * the IO */ @@ -661,7 +650,7 @@ pub fn parse_packages(build_state: &mut BuildState) { Some(source_files) => source_files .keys() .map(|key| key.to_owned()) - .collect::>(), + .collect::>(), None => unreachable!(), }; let entry = match &package.namespace { @@ -725,9 +714,8 @@ pub fn parse_packages(build_state: &mut BuildState) { Some(source_files) => source_files.iter().for_each(|(file, metadata)| { let namespace = package.namespace.to_owned(); - let file_buf = PathBuf::from(file); - let extension = file_buf.extension().unwrap().to_str().unwrap(); - let module_name = helpers::file_path_to_module_name(&file.to_owned(), &namespace); + let extension = file.extension().unwrap().to_str().unwrap(); + let module_name = helpers::file_path_to_module_name(&file, &namespace); if helpers::is_implementation_file(extension) { build_state @@ -737,8 +725,11 @@ pub fn parse_packages(build_state: &mut BuildState) { if let SourceType::SourceFile(ref mut source_file) = module.source_type { if &source_file.implementation.path != file { error!("Duplicate files found for module: {}", &module_name); - error!("file 1: {}", &source_file.implementation.path); - error!("file 2: {}", &file); + error!( + "file 1: {}", + source_file.implementation.path.to_string_lossy() + ); + error!("file 2: {}", file.to_string_lossy()); panic!("Unable to continue... See log output above..."); } @@ -769,13 +760,19 @@ pub fn parse_packages(build_state: &mut BuildState) { } else { // remove last character of string: resi -> res, rei -> re, mli -> ml let mut implementation_filename = file.to_owned(); - implementation_filename.pop(); + let extension = implementation_filename.extension().unwrap().to_str().unwrap(); + implementation_filename = match extension { + "resi" => implementation_filename.with_extension("res"), + "rei" => implementation_filename.with_extension("re"), + "mli" => implementation_filename.with_extension("ml"), + _ => implementation_filename, + }; match source_files.get(&implementation_filename) { None => { - log::warn!( + println!( "{} No implementation file found for interface file (skipping): {}", LINE_CLEAR, - file + file.to_string_lossy() ) } Some(_) => { @@ -800,7 +797,7 @@ pub fn parse_packages(build_state: &mut BuildState) { source_type: SourceType::SourceFile(SourceFile { // this will be overwritten later implementation: Implementation { - path: implementation_filename.to_string(), + path: implementation_filename, parse_state: ParseState::Pending, compile_state: CompileState::Pending, last_modified: metadata.modified, @@ -943,10 +940,10 @@ pub fn validate_packages_dependencies(packages: &AHashMap) -> b #[cfg(test)] mod test { + use super::{Namespace, Package}; use crate::config::Source; use ahash::{AHashMap, AHashSet}; - - use super::{Namespace, Package}; + use std::path::PathBuf; fn create_package( name: String, @@ -982,7 +979,7 @@ mod test { source_files: None, namespace: Namespace::Namespace(String::from("Package1")), modules: None, - path: String::from("./something"), + path: PathBuf::from("./something"), dirs: None, is_pinned_dep: false, is_root: false, diff --git a/rewatch/src/build/parse.rs b/rewatch/src/build/parse.rs index b4fddd72f2..f4f49a28c3 100644 --- a/rewatch/src/build/parse.rs +++ b/rewatch/src/build/parse.rs @@ -74,17 +74,13 @@ pub fn generate_asts( } else { ( Ok(( - Path::new( - &(helpers::get_basename(&source_file.implementation.path).to_string() - + ".ast"), - ) - .to_path_buf(), + PathBuf::from(helpers::get_basename(&source_file.implementation.path)) + .with_extension("ast"), None, )), Ok(source_file.interface.as_ref().map(|i| { ( - Path::new(&(helpers::get_basename(&i.path).to_string() + ".iast")) - .to_path_buf(), + PathBuf::from(helpers::get_basename(&i.path)).with_extension("iast"), None, ) })), @@ -213,23 +209,23 @@ pub fn generate_asts( .namespace .to_suffix() .expect("namespace should be set for mlmap module"); - let base_build_path = package.get_build_path() + "/" + &suffix; - let base_ocaml_build_path = package.get_ocaml_build_path() + "/" + &suffix; + let base_build_path = package.get_build_path().join(&suffix); + let base_ocaml_build_path = package.get_ocaml_build_path().join(&suffix); let _ = std::fs::copy( - base_build_path.to_string() + ".cmi", - base_ocaml_build_path.to_string() + ".cmi", + base_build_path.with_extension("cmi"), + base_ocaml_build_path.with_extension("cmi"), ); let _ = std::fs::copy( - base_build_path.to_string() + ".cmt", - base_ocaml_build_path.to_string() + ".cmt", + base_build_path.with_extension("cmt"), + base_ocaml_build_path.with_extension("cmt"), ); let _ = std::fs::copy( - base_build_path.to_string() + ".cmj", - base_ocaml_build_path.to_string() + ".cmj", + base_build_path.with_extension("cmj"), + base_ocaml_build_path.with_extension("cmj"), ); let _ = std::fs::copy( - base_build_path.to_string() + ".mlmap", - base_ocaml_build_path.to_string() + ".mlmap", + base_build_path.with_extension("mlmap"), + base_ocaml_build_path.with_extension("mlmap"), ); match (mlmap_hash, mlmap_hash_after) { (Some(digest), Some(digest_after)) => !digest.eq(&digest_after), @@ -256,19 +252,19 @@ pub fn generate_asts( pub fn parser_args( config: &config::Config, root_config: &config::Config, - filename: &str, + filename: &Path, version: &str, - workspace_root: &Option, - root_path: &str, + workspace_root: &Option, + root_path: &Path, contents: &str, ) -> (PathBuf, Vec) { - let file = &filename.to_string(); + let file = &filename; let ast_path = helpers::get_ast_path(file); let ppx_flags = config::flatten_ppx_flags( &if let Some(workspace_root) = workspace_root { - format!("{}/node_modules", &workspace_root) + workspace_root.join("node_modules") } else { - format!("{}/node_modules", &root_path) + root_path.join("node_modules") }, &filter_ppx_flags(&config.ppx_flags, contents), &config.name, @@ -279,7 +275,8 @@ pub fn parser_args( let uncurried_args = root_config.get_uncurried_args(version); let bsc_flags = config::flatten_flags(&config.bsc_flags); - let file = "../../".to_string() + file; + let file = PathBuf::from("..").join("..").join(file); + ( ast_path.to_owned(), [ @@ -295,7 +292,7 @@ pub fn parser_args( "-bs-ast".to_string(), "-o".to_string(), ast_path.to_string_lossy().to_string(), - file, + file.to_string_lossy().to_string(), ], ] .concat(), @@ -305,10 +302,10 @@ pub fn parser_args( fn generate_ast( package: packages::Package, root_package: packages::Package, - filename: &str, + filename: &Path, version: &str, - bsc_path: &str, - workspace_root: &Option, + bsc_path: &PathBuf, + workspace_root: &Option, ) -> Result<(PathBuf, Option), String> { let file_path = PathBuf::from(&package.path).join(filename); let contents = helpers::read_file(&file_path).expect("Error reading file"); @@ -325,7 +322,8 @@ fn generate_ast( ); // generate the dir of the ast_path (it mirrors the source file dir) - helpers::create_path(&(package.get_build_path() + "/" + &ast_path.parent().unwrap().to_string_lossy())); + let ast_parent_path = package.get_build_path().join(ast_path.parent().unwrap()); + helpers::create_path(&ast_parent_path); /* Create .ast */ let result = if let Some(res_to_ast) = Some( @@ -346,17 +344,18 @@ fn generate_ast( Ok((ast_path, None)) } } else { - log::info!("Parsing file {}...", filename); + log::info!("Parsing file {}...", filename.display()); Err(format!( "Could not find canonicalize_string_path for file {} in package {}", - filename, package.name + filename.display(), + package.name )) }; if let Ok((ast_path, _)) = &result { let _ = std::fs::copy( Path::new(&build_path_abs).join(&ast_path), - std::path::Path::new(&package.get_ocaml_build_path()).join(ast_path.file_name().unwrap()), + package.get_ocaml_build_path().join(ast_path.file_name().unwrap()), ); } result diff --git a/rewatch/src/build/read_compile_state.rs b/rewatch/src/build/read_compile_state.rs index 4bfc3e29fc..c6a05e445d 100644 --- a/rewatch/src/build/read_compile_state.rs +++ b/rewatch/src/build/read_compile_state.rs @@ -4,11 +4,11 @@ use crate::helpers; use ahash::{AHashMap, AHashSet}; use rayon::prelude::*; use std::fs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::time::SystemTime; pub fn read(build_state: &mut BuildState) -> CompileAssetsState { - let mut ast_modules: AHashMap = AHashMap::new(); + let mut ast_modules: AHashMap = AHashMap::new(); let mut cmi_modules: AHashMap = AHashMap::new(); let mut cmt_modules: AHashMap = AHashMap::new(); let mut ast_rescript_file_locations = AHashSet::new(); @@ -20,16 +20,11 @@ pub fn read(build_state: &mut BuildState) -> CompileAssetsState { SourceType::SourceFile(source_file) => { let package = build_state.packages.get(&module.package_name).unwrap(); - Some( - PathBuf::from(&package.path) - .join(&source_file.implementation.path) - .to_string_lossy() - .to_string(), - ) + Some(PathBuf::from(&package.path).join(&source_file.implementation.path)) } _ => None, }) - .collect::>(); + .collect::>(); rescript_file_locations.extend( build_state @@ -37,14 +32,12 @@ pub fn read(build_state: &mut BuildState) -> CompileAssetsState { .values() .filter_map(|module| { let package = build_state.packages.get(&module.package_name).unwrap(); - module.get_interface().as_ref().map(|interface| { - PathBuf::from(&package.path) - .join(&interface.path) - .to_string_lossy() - .to_string() - }) + module + .get_interface() + .as_ref() + .map(|interface| package.path.join(&interface.path)) }) - .collect::>(), + .collect::>(), ); // scan all ast files in all packages @@ -52,7 +45,7 @@ pub fn read(build_state: &mut BuildState) -> CompileAssetsState { .packages .par_iter() .map(|(_, package)| { - let read_dir = fs::read_dir(std::path::Path::new(&package.get_ocaml_build_path())).unwrap(); + let read_dir = fs::read_dir(&package.get_ocaml_build_path()).unwrap(); read_dir .filter_map(|entry| match entry { Ok(entry) => { @@ -84,36 +77,33 @@ pub fn read(build_state: &mut BuildState) -> CompileAssetsState { |(path, last_modified, extension, package_name, package_namespace, package_is_root)| { match extension.as_str() { "iast" | "ast" => { - let module_name = - helpers::file_path_to_module_name(path.to_str().unwrap(), package_namespace); + let module_name = helpers::file_path_to_module_name(path, package_namespace); - let ast_file_path = path.to_str().unwrap().to_owned(); - let res_file_path = get_res_path_from_ast(&ast_file_path); let root_package = build_state .packages .get(&build_state.root_config_name) .expect("Could not find root package"); - if let Some(res_file_path) = res_file_path { + if let Some(res_file_path_buf) = get_res_path_from_ast(&path) { let _ = ast_modules.insert( - res_file_path.to_owned(), + res_file_path_buf.clone(), AstModule { module_name, package_name: package_name.to_owned(), namespace: package_namespace.to_owned(), last_modified: last_modified.to_owned(), - ast_file_path, + ast_file_path: path.to_path_buf(), is_root: *package_is_root, suffix: root_package .config .get_suffix(root_package.config.get_package_specs().first().unwrap()), }, ); - let _ = ast_rescript_file_locations.insert(res_file_path); + let _ = ast_rescript_file_locations.insert(res_file_path_buf); } } "cmi" => { let module_name = helpers::file_path_to_module_name( - path.to_str().unwrap(), + path, // we don't want to include a namespace here because the CMI file // already includes a namespace &packages::Namespace::NoNamespace, @@ -122,7 +112,7 @@ pub fn read(build_state: &mut BuildState) -> CompileAssetsState { } "cmt" => { let module_name = helpers::file_path_to_module_name( - path.to_str().unwrap(), + path, // we don't want to include a namespace here because the CMI file // already includes a namespace &packages::Namespace::NoNamespace, @@ -145,15 +135,15 @@ pub fn read(build_state: &mut BuildState) -> CompileAssetsState { } } -fn get_res_path_from_ast(ast_file: &str) -> Option { - if let Ok(lines) = helpers::read_lines(ast_file.to_string()) { +fn get_res_path_from_ast(ast_file: &Path) -> Option { + if let Ok(lines) = helpers::read_lines(ast_file) { // we skip the first line with is some null characters // the following lines in the AST are the dependency modules - // we stop when we hit a line that starts with a "/", this is the path of the file. + // we stop when we hit a line that is an absolute path, this is the path of the file. // this is the point where the dependencies end and the actual AST starts for line in lines.skip(1) { match line { - Ok(line) if line.trim_start().starts_with('/') => return Some(line), + Ok(line) if Path::new(&line).is_absolute() => return Some(PathBuf::from(line)), _ => (), } } diff --git a/rewatch/src/config.rs b/rewatch/src/config.rs index 7bcbda2e51..d9706086ad 100644 --- a/rewatch/src/config.rs +++ b/rewatch/src/config.rs @@ -246,13 +246,13 @@ pub fn flatten_flags(flags: &Option>>) -> Vec { /// Since ppx-flags could be one or more, and could be nested potentiall, this function takes the /// flags and flattens them outright. pub fn flatten_ppx_flags( - node_modules_dir: &String, + node_modules_dir: &Path, flags: &Option>>, package_name: &String, ) -> Vec { match flags { None => vec![], - Some(xs) => xs + Some(flags) => flags .iter() .flat_map(|x| match x { OneOrMore::Single(y) => { @@ -261,18 +261,29 @@ pub fn flatten_ppx_flags( Some('.') => { vec![ "-ppx".to_string(), - node_modules_dir.to_owned() + "/" + package_name + "/" + y, + node_modules_dir + .join(package_name) + .join(y) + .to_string_lossy() + .to_string(), ] } - _ => vec!["-ppx".to_string(), node_modules_dir.to_owned() + "/" + y], + _ => vec![ + "-ppx".to_string(), + node_modules_dir.join(y).to_string_lossy().to_string(), + ], } } OneOrMore::Multiple(ys) if ys.is_empty() => vec![], OneOrMore::Multiple(ys) => { let first_character = ys[0].chars().next(); let ppx = match first_character { - Some('.') => node_modules_dir.to_owned() + "/" + package_name + "/" + &ys[0], - _ => node_modules_dir.to_owned() + "/" + &ys[0], + Some('.') => node_modules_dir + .join(package_name) + .join(&ys[0]) + .to_string_lossy() + .to_string(), + _ => node_modules_dir.join(&ys[0]).to_string_lossy().to_string(), }; vec![ "-ppx".to_string(), @@ -289,8 +300,8 @@ pub fn flatten_ppx_flags( } /// Try to convert a bsconfig from a certain path to a bsconfig struct -pub fn read(path: String) -> Result { - let read = fs::read_to_string(path.clone())?; +pub fn read(path: &Path) -> Result { + let read = fs::read_to_string(path)?; let parse = serde_json::from_str::(&read)?; Ok(parse) diff --git a/rewatch/src/helpers.rs b/rewatch/src/helpers.rs index 58fb734154..a91dee77f5 100644 --- a/rewatch/src/helpers.rs +++ b/rewatch/src/helpers.rs @@ -36,6 +36,53 @@ pub mod emojis { pub static LINE_CLEAR: &str = "\x1b[2K\r"; } +/// This trait is used to strip the verbatim prefix from a Windows path. +/// On non-Windows systems, it simply returns the original path. +/// This is needed until the rescript compiler can handle such paths. +pub trait StrippedVerbatimPath { + fn to_stripped_verbatim_path(self) -> PathBuf; +} + +impl StrippedVerbatimPath for PathBuf { + fn to_stripped_verbatim_path(self) -> PathBuf { + if cfg!(not(target_os = "windows")) { + return self; + } + + let mut stripped = PathBuf::new(); + for component in self.components() { + match component { + Component::Prefix(prefix_component) => { + if prefix_component.kind().is_verbatim() { + stripped.push( + prefix_component + .as_os_str() + .to_string_lossy() + .strip_prefix("\\\\?\\") + .unwrap(), + ); + } else { + stripped.push(prefix_component.as_os_str()); + } + } + Component::RootDir => { + stripped.push(Component::RootDir); + } + Component::CurDir => { + stripped.push(Component::CurDir); + } + Component::ParentDir => { + stripped.push(Component::ParentDir); + } + Component::Normal(os_str) => { + stripped.push(Component::Normal(os_str)); + } + } + } + stripped + } +} + pub trait LexicalAbsolute { fn to_lexical_absolute(&self) -> std::io::Result; } @@ -60,24 +107,20 @@ impl LexicalAbsolute for Path { } } -pub fn package_path(root: &str, package_name: &str) -> String { - format!("{}/node_modules/{}", root, package_name) +pub fn package_path(root: &Path, package_name: &str) -> PathBuf { + root.join("node_modules").join(package_name) } -pub fn get_abs_path(path: &str) -> String { +pub fn get_abs_path(path: &Path) -> PathBuf { let abs_path_buf = PathBuf::from(path); return abs_path_buf .to_lexical_absolute() - .expect("Could not canonicalize") - .to_str() - .expect("Could not canonicalize") - .to_string(); + .expect("Could not canonicalize"); } -pub fn get_basename(path: &str) -> String { - let path_buf = PathBuf::from(path); - return path_buf +pub fn get_basename(path: &Path) -> String { + return path .file_stem() .expect("Could not get basename") .to_str() @@ -85,15 +128,6 @@ pub fn get_basename(path: &str) -> String { .to_string(); } -pub fn change_extension(path: &str, new_extension: &str) -> String { - let path_buf = PathBuf::from(path); - return path_buf - .with_extension(new_extension) - .to_str() - .expect("Could not change extension") - .to_string(); -} - /// Capitalizes the first character in s. fn capitalize(s: &str) -> String { let mut c = s.chars(); @@ -121,12 +155,12 @@ pub fn module_name_with_namespace(module_name: &str, namespace: &packages::Names // this doesn't capitalize the module name! if the rescript name of the file is "foo.res" the // compiler assets are foo-Namespace.cmt and foo-Namespace.cmj, but the module name is Foo -pub fn file_path_to_compiler_asset_basename(path: &str, namespace: &packages::Namespace) -> String { +pub fn file_path_to_compiler_asset_basename(path: &Path, namespace: &packages::Namespace) -> String { let base = get_basename(path); add_suffix(&base, namespace) } -pub fn file_path_to_module_name(path: &str, namespace: &packages::Namespace) -> String { +pub fn file_path_to_module_name(path: &Path, namespace: &packages::Namespace) -> String { capitalize(&file_path_to_compiler_asset_basename(path, namespace)) } @@ -139,18 +173,15 @@ pub fn contains_ascii_characters(str: &str) -> bool { false } -pub fn create_path(path: &str) { - fs::DirBuilder::new() - .recursive(true) - .create(PathBuf::from(path.to_string())) - .unwrap(); +pub fn create_path(path: &Path) { + fs::DirBuilder::new().recursive(true).create(path).unwrap(); } pub fn create_path_for_path(path: &Path) { fs::DirBuilder::new().recursive(true).create(path).unwrap(); } -pub fn get_bsc(root_path: &str, workspace_root: Option) -> String { +pub fn get_bsc(root_path: &Path, workspace_root: &Option) -> PathBuf { let subfolder = match (std::env::consts::OS, std::env::consts::ARCH) { ("macos", "aarch64") => "darwin-arm64", ("macos", _) => "darwin-x64", @@ -162,25 +193,29 @@ pub fn get_bsc(root_path: &str, workspace_root: Option) -> String { }; match ( - PathBuf::from(format!( - "{}/node_modules/@rescript/{}/bin/bsc.exe", - root_path, subfolder - )) - .canonicalize(), - workspace_root.map(|workspace_root| { - PathBuf::from(format!( - "{}/node_modules/rescript/{}/bsc.exe", - workspace_root, subfolder - )) + root_path + .join("node_modules") + .join("@rescript") + .join(subfolder) + .join("bin") + .join("bsc.exe") .canonicalize() + .map(StrippedVerbatimPath::to_stripped_verbatim_path), + workspace_root.as_ref().map(|workspace_root| { + workspace_root + .join("node_modules") + .join("@rescript") + .join(subfolder) + .join("bin") + .join("bsc.exe") + .canonicalize() + .map(StrippedVerbatimPath::to_stripped_verbatim_path) }), ) { (Ok(path), _) => path, (_, Some(Ok(path))) => path, _ => panic!("Could not find bsc.exe"), } - .to_string_lossy() - .to_string() } pub fn string_ends_with_any(s: &Path, suffixes: &[&str]) -> bool { @@ -198,40 +233,44 @@ fn path_to_ast_extension(path: &Path) -> &str { } } -pub fn get_ast_path(source_file: &str) -> PathBuf { - let source_path = Path::new(source_file); +pub fn get_ast_path(source_file: &Path) -> PathBuf { + let source_path = source_file; + let basename = file_path_to_compiler_asset_basename(source_file, &packages::Namespace::NoNamespace); + let extension = path_to_ast_extension(source_path); - source_path.parent().unwrap().join( - file_path_to_compiler_asset_basename(source_file, &packages::Namespace::NoNamespace) - + path_to_ast_extension(source_path), - ) + source_path + .parent() + .unwrap() + .join(format!("{}{}", basename, extension)) } pub fn get_compiler_asset( package: &packages::Package, namespace: &packages::Namespace, - source_file: &str, + source_file: &Path, extension: &str, -) -> String { +) -> PathBuf { let namespace = match extension { "ast" | "iast" => &packages::Namespace::NoNamespace, _ => namespace, }; - package.get_ocaml_build_path() - + "/" - + &file_path_to_compiler_asset_basename(source_file, namespace) - + "." - + extension + let basename = file_path_to_compiler_asset_basename(source_file, namespace); + package + .get_ocaml_build_path() + .join(format!("{}.{}", basename, extension)) } pub fn canonicalize_string_path(path: &str) -> Option { - return Path::new(path).canonicalize().ok(); + return Path::new(path) + .canonicalize() + .map(StrippedVerbatimPath::to_stripped_verbatim_path) + .ok(); } pub fn get_bs_compiler_asset( package: &packages::Package, namespace: &packages::Namespace, - source_file: &str, + source_file: &Path, extension: &str, ) -> String { let namespace = match extension { @@ -239,11 +278,13 @@ pub fn get_bs_compiler_asset( _ => namespace, }; - let dir = std::path::Path::new(&source_file).parent().unwrap(); + let dir = source_file.parent().unwrap(); + let basename = file_path_to_compiler_asset_basename(source_file, namespace); - std::path::Path::new(&package.get_build_path()) + package + .get_build_path() .join(dir) - .join(file_path_to_compiler_asset_basename(source_file, namespace) + extension) + .join(format!("{}{}", basename, extension)) .to_str() .unwrap() .to_owned() @@ -255,11 +296,13 @@ pub fn get_namespace_from_module_name(module_name: &str) -> Option { split.next().map(|s| s.to_string()) } -pub fn is_interface_ast_file(file: &str) -> bool { - file.ends_with(".iast") +pub fn is_interface_ast_file(file: &Path) -> bool { + file.extension() + .map(|extension| extension.eq_ignore_ascii_case("iast")) + .unwrap_or(false) } -pub fn read_lines(filename: String) -> io::Result>> { +pub fn read_lines(filename: &Path) -> io::Result>> { let file = fs::File::open(filename)?; Ok(io::BufReader::new(file).lines()) } @@ -290,9 +333,8 @@ pub fn is_non_exotic_module_name(module_name: &str) -> bool { false } -pub fn get_extension(path: &str) -> String { - let path_buf = PathBuf::from(path); - return path_buf +pub fn get_extension(path: &Path) -> String { + return path .extension() .expect("Could not get extension") .to_str() @@ -324,18 +366,18 @@ fn has_rescript_config(path: &Path) -> bool { path.join("bsconfig.json").exists() || path.join("rescript.json").exists() } -pub fn get_workspace_root(package_root: &str) -> Option { +pub fn get_workspace_root(package_root: &Path) -> Option { std::path::PathBuf::from(&package_root) .parent() .and_then(get_nearest_config) } // traverse up the directory tree until we find a config.json, if not return None -pub fn get_nearest_config(path_buf: &Path) -> Option { +pub fn get_nearest_config(path_buf: &Path) -> Option { let mut current_dir = path_buf.to_owned(); loop { if has_rescript_config(¤t_dir) { - return Some(current_dir.to_string_lossy().to_string()); + return Some(current_dir); } match current_dir.parent() { None => return None, @@ -344,7 +386,7 @@ pub fn get_nearest_config(path_buf: &Path) -> Option { } } -pub fn get_rescript_version(bsc_path: &str) -> String { +pub fn get_rescript_version(bsc_path: &Path) -> String { let version_cmd = Command::new(bsc_path) .args(["-v"]) .output() diff --git a/rewatch/src/lock.rs b/rewatch/src/lock.rs index dfadcfbb4e..9eca7ed604 100644 --- a/rewatch/src/lock.rs +++ b/rewatch/src/lock.rs @@ -22,8 +22,14 @@ impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let msg = match self { Error::Locked(pid) => format!("Rewatch is already running. The process ID (PID) is {}", pid), - Error::ParsingLockfile(e) => format!("Could not parse lockfile: \n {} \n (try removing it and running the command again)", e), - Error::ReadingLockfile(e) => format!("Could not read lockfile: \n {} \n (try removing it and running the command again)", e), + Error::ParsingLockfile(e) => format!( + "Could not parse lockfile: \n {} \n (try removing it and running the command again)", + e + ), + Error::ReadingLockfile(e) => format!( + "Could not read lockfile: \n {} \n (try removing it and running the command again)", + e + ), Error::WritingLockfile(e) => format!("Could not write lockfile: \n {}", e), }; write!(f, "{}", msg) @@ -54,15 +60,14 @@ fn create(lockfile_location: &Path, pid: u32) -> Lock { } pub fn get(folder: &str) -> Lock { - let location = format!("{}/lib/{}", folder, LOCKFILE); - let path = Path::new(&location); + let location = Path::new(folder).join("lib").join(LOCKFILE); let pid = process::id(); match fs::read_to_string(&location) { - Err(e) if (e.kind() == std::io::ErrorKind::NotFound) => create(path, pid), + Err(e) if (e.kind() == std::io::ErrorKind::NotFound) => create(&location, pid), Err(e) => Lock::Error(Error::ReadingLockfile(e)), Ok(s) => match s.parse::() { - Ok(parsed_pid) if !exists(parsed_pid) => create(path, pid), + Ok(parsed_pid) if !exists(parsed_pid) => create(&location, pid), Ok(parsed_pid) => Lock::Error(Error::Locked(parsed_pid)), Err(e) => Lock::Error(Error::ParsingLockfile(e)), }, diff --git a/rewatch/src/main.rs b/rewatch/src/main.rs index 3ff476875e..5c27a4d02b 100644 --- a/rewatch/src/main.rs +++ b/rewatch/src/main.rs @@ -4,6 +4,7 @@ use clap_verbosity_flag::InfoLevel; use log::LevelFilter; use regex::Regex; use std::io::Write; +use std::path::{Path, PathBuf}; use rewatch::{build, cmd, lock, watcher}; @@ -45,6 +46,10 @@ struct Args { #[arg(short, long, default_value = "false", num_args = 0..=1)] no_timing: bool, + // simple output for snapshot testing + #[arg(short, long, default_value = "false", num_args = 0..=1)] + snapshot_output: bool, + /// Verbosity: /// -v -> Debug /// -vv -> Trace @@ -102,7 +107,12 @@ fn main() -> Result<()> { Some(path) => { println!( "{}", - build::get_compiler_args(&path, args.rescript_version, args.bsc_path, args.dev)? + build::get_compiler_args( + Path::new(&path), + args.rescript_version, + &args.bsc_path.map(PathBuf::from), + args.dev + )? ); std::process::exit(0); } @@ -118,16 +128,23 @@ fn main() -> Result<()> { std::process::exit(1) } lock::Lock::Aquired(_) => match command { - Command::Clean => build::clean::clean(&folder, show_progress, args.bsc_path, args.dev), + Command::Clean => build::clean::clean( + Path::new(&folder), + show_progress, + &args.bsc_path.map(PathBuf::from), + args.dev, + args.snapshot_output, + ), Command::Build => { match build::build( &filter, - &folder, + Path::new(&folder), show_progress, args.no_timing, args.create_sourcedirs, - args.bsc_path, + &args.bsc_path.map(PathBuf::from), args.dev, + args.snapshot_output, ) { Err(e) => { println!("{e}"); @@ -150,6 +167,7 @@ fn main() -> Result<()> { args.create_sourcedirs, args.dev, args.bsc_path, + args.snapshot_output, ); Ok(()) diff --git a/rewatch/src/sourcedirs.rs b/rewatch/src/sourcedirs.rs index ed704aa7e9..46325b3842 100644 --- a/rewatch/src/sourcedirs.rs +++ b/rewatch/src/sourcedirs.rs @@ -6,11 +6,11 @@ use serde::Serialize; use serde_json::json; use std::fs::File; use std::io::prelude::*; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; -type Dir = String; +type Dir = PathBuf; type PackageName = String; -type AbsolutePath = String; +type AbsolutePath = PathBuf; type Pkg = (PackageName, AbsolutePath); #[derive(Serialize, Debug, Clone, PartialEq, Hash)] @@ -20,20 +20,16 @@ pub struct SourceDirs<'a> { pub generated: &'a Vec, } -fn package_to_dirs(package: &Package, root_package_path: &String) -> AHashSet { - let relative_path = PathBuf::from(&package.path) - .strip_prefix(PathBuf::from(&root_package_path)) - .unwrap() - .to_string_lossy() - .to_string(); +fn package_to_dirs(package: &Package, root_package_path: &Path) -> AHashSet { + let relative_path = package.path.strip_prefix(&root_package_path).unwrap(); package .dirs .as_ref() .unwrap_or(&AHashSet::new()) .iter() - .filter_map(|path| path.to_str().map(|path| format!("{relative_path}/{path}"))) - .collect::>() + .map(|path| relative_path.join(path)) + .collect::>() } fn deps_to_pkgs<'a>( @@ -52,8 +48,8 @@ fn deps_to_pkgs<'a>( .collect::>() } -fn write_sourcedirs_files(path: String, source_dirs: &SourceDirs) -> Result { - let mut source_dirs_json = File::create(path + "/.sourcedirs.json")?; +fn write_sourcedirs_files(path: &Path, source_dirs: &SourceDirs) -> Result { + let mut source_dirs_json = File::create(path.join(".sourcedirs.json"))?; source_dirs_json.write(json!(source_dirs).to_string().as_bytes()) } @@ -85,7 +81,7 @@ pub fn print(buildstate: &BuildState) { // Write sourcedirs.json write_sourcedirs_files( - package.get_build_path(), + &package.get_build_path(), &SourceDirs { dirs: &dirs.clone().into_iter().collect::>(), pkgs: &pkgs.clone().flatten().collect::>(), @@ -109,7 +105,7 @@ pub fn print(buildstate: &BuildState) { // Write sourcedirs.json write_sourcedirs_files( - root_package.get_build_path(), + &root_package.get_build_path(), &SourceDirs { dirs: &merged_dirs.into_iter().collect::>(), pkgs: &merged_pkgs.into_iter().collect::>(), diff --git a/rewatch/src/watcher.rs b/rewatch/src/watcher.rs index 6b99db75bc..9151028937 100644 --- a/rewatch/src/watcher.rs +++ b/rewatch/src/watcher.rs @@ -4,12 +4,13 @@ use crate::build::clean; use crate::cmd; use crate::helpers; use crate::helpers::emojis::*; +use crate::helpers::StrippedVerbatimPath; use crate::queue::FifoQueue; use crate::queue::*; use futures_timer::Delay; use notify::event::ModifyKind; use notify::{Config, Error, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::Arc; use std::sync::Mutex; use std::time::{Duration, Instant}; @@ -32,10 +33,17 @@ fn is_rescript_file(path_buf: &Path) -> bool { } fn is_in_build_path(path_buf: &Path) -> bool { - path_buf - .to_str() - .map(|x| x.contains("/lib/bs/") || x.contains("/lib/ocaml/")) - .unwrap_or(false) + let mut prev_component: Option<&std::ffi::OsStr> = None; + for component in path_buf.components() { + let comp_os = component.as_os_str(); + if let Some(prev) = prev_component { + if prev == "lib" && (comp_os == "bs" || comp_os == "ocaml") { + return true; + } + } + prev_component = Some(comp_os); + } + false } fn matches_filter(path_buf: &Path, filter: &Option) -> bool { @@ -48,21 +56,23 @@ fn matches_filter(path_buf: &Path, filter: &Option) -> bool { async fn async_watch( q: Arc>>, - path: &str, + path: &Path, show_progress: bool, filter: &Option, after_build: Option, create_sourcedirs: bool, build_dev_deps: bool, - bsc_path: Option, + bsc_path: Option, + snapshot_output: bool, ) -> notify::Result<()> { let mut build_state = build::initialize_build( None, filter, show_progress, path, - bsc_path.clone(), + &bsc_path, build_dev_deps, + snapshot_output, ) .expect("Can't initialize build"); let mut needs_compile_type = CompileType::Incremental; @@ -143,7 +153,10 @@ async fn async_watch( ) => { // if we are going to compile incrementally, we need to mark the exact files // dirty - if let Ok(canonicalized_path_buf) = path_buf.canonicalize() { + if let Ok(canonicalized_path_buf) = path_buf + .canonicalize() + .map(StrippedVerbatimPath::to_stripped_verbatim_path) + { for module in build_state.modules.values_mut() { match module.source_type { SourceType::SourceFile(ref mut source_file) => { @@ -153,8 +166,7 @@ async fn async_watch( .get(&module.package_name) .expect("Package not found"); let canonicalized_implementation_file = - std::path::PathBuf::from(package.path.to_string()) - .join(&source_file.implementation.path); + package.path.join(&source_file.implementation.path); if canonicalized_path_buf == canonicalized_implementation_file { if let Ok(modified) = canonicalized_path_buf.metadata().and_then(|x| x.modified()) @@ -168,8 +180,7 @@ async fn async_watch( // mark the interface file dirty if let Some(ref mut interface) = source_file.interface { let canonicalized_interface_file = - std::path::PathBuf::from(package.path.to_string()) - .join(&interface.path); + package.path.join(&interface.path); if canonicalized_path_buf == canonicalized_interface_file { if let Ok(modified) = canonicalized_path_buf .metadata() @@ -214,6 +225,7 @@ async fn async_watch( !initial_build, create_sourcedirs, build_dev_deps, + snapshot_output, ) .is_ok() { @@ -222,13 +234,18 @@ async fn async_watch( } let timing_total_elapsed = timing_total.elapsed(); if show_progress { - println!( - "\n{}{}Finished {} compilation in {:.2}s\n", - LINE_CLEAR, - SPARKLES, - if initial_build { "initial" } else { "incremental" }, - timing_total_elapsed.as_secs_f64() - ); + let compilation_type = if initial_build { "initial" } else { "incremental" }; + if snapshot_output { + println!("Finished {} compilation", compilation_type) + } else { + println!( + "\n{}{}Finished {} compilation in {:.2}s\n", + LINE_CLEAR, + SPARKLES, + compilation_type, + timing_total_elapsed.as_secs_f64() + ); + } } } needs_compile_type = CompileType::None; @@ -241,8 +258,9 @@ async fn async_watch( filter, show_progress, path, - bsc_path.clone(), + &bsc_path, build_dev_deps, + snapshot_output, ) .expect("Can't initialize build"); let _ = build::incremental_build( @@ -253,6 +271,7 @@ async fn async_watch( false, create_sourcedirs, build_dev_deps, + snapshot_output, ); if let Some(a) = after_build.clone() { cmd::run(a) @@ -261,7 +280,7 @@ async fn async_watch( build::write_build_ninja(&build_state); let timing_total_elapsed = timing_total.elapsed(); - if show_progress { + if !snapshot_output && show_progress { println!( "\n{}{}Finished compilation in {:.2}s\n", LINE_CLEAR, @@ -289,6 +308,7 @@ pub fn start( create_sourcedirs: bool, build_dev_deps: bool, bsc_path: Option, + snapshot_output: bool, ) { futures::executor::block_on(async { let queue = Arc::new(FifoQueue::>::new()); @@ -301,15 +321,19 @@ pub fn start( .watch(folder.as_ref(), RecursiveMode::Recursive) .expect("Could not start watcher"); + let path = Path::new(folder); + let bsc_path_buf = bsc_path.map(PathBuf::from); + if let Err(e) = async_watch( consumer, - folder, + path, show_progress, filter, after_build, create_sourcedirs, build_dev_deps, - bsc_path, + bsc_path_buf, + snapshot_output, ) .await { diff --git a/rewatch/testrepo/package.json b/rewatch/testrepo/package.json index 7f50287c62..3436184038 100644 --- a/rewatch/testrepo/package.json +++ b/rewatch/testrepo/package.json @@ -12,7 +12,7 @@ ] }, "dependencies": { - "rescript": "11.1.4" + "rescript": "12.0.0-alpha.13" }, "scripts": { "build": "../target/release/rewatch build .", diff --git a/rewatch/testrepo/packages/namespace-casing/package.json b/rewatch/testrepo/packages/namespace-casing/package.json index 65b2411476..a666649db8 100644 --- a/rewatch/testrepo/packages/namespace-casing/package.json +++ b/rewatch/testrepo/packages/namespace-casing/package.json @@ -5,8 +5,5 @@ "rescript" ], "author": "", - "license": "MIT", - "dependencies": { - "rescript": "*" - } + "license": "MIT" } diff --git a/rewatch/testrepo/packages/with-dev-deps/package.json b/rewatch/testrepo/packages/with-dev-deps/package.json index 68aad6fe20..007ab59b9a 100644 --- a/rewatch/testrepo/packages/with-dev-deps/package.json +++ b/rewatch/testrepo/packages/with-dev-deps/package.json @@ -5,8 +5,5 @@ "rescript" ], "author": "", - "license": "MIT", - "dependencies": { - "@rescript/core": "*" - } + "license": "MIT" } diff --git a/rewatch/testrepo/packages/with-dev-deps/rescript.json b/rewatch/testrepo/packages/with-dev-deps/rescript.json index 6d67d606cc..8cae5b6dcc 100644 --- a/rewatch/testrepo/packages/with-dev-deps/rescript.json +++ b/rewatch/testrepo/packages/with-dev-deps/rescript.json @@ -13,6 +13,5 @@ "module": "es6", "in-source": true }, - "bs-dependencies": ["@rescript/core"], "suffix": ".res.js" } diff --git a/rewatch/testrepo/yarn.lock b/rewatch/testrepo/yarn.lock index 01d1a28761..ac41b3d2c1 100644 --- a/rewatch/testrepo/yarn.lock +++ b/rewatch/testrepo/yarn.lock @@ -5,12 +5,38 @@ __metadata: version: 8 cacheKey: 10c0 -"@rescript/core@npm:*": - version: 1.6.1 - resolution: "@rescript/core@npm:1.6.1" - peerDependencies: - rescript: ">=11.1.0" - checksum: 10c0/04e7fc14dc050de0a9da8eb0642fb0800bc80c8978ef79aada1823bc2080b47a4d40c1168c63b53642009935342a4feb833fc190a71aa45e83af5d896269b95d +"@rescript/darwin-arm64@npm:12.0.0-alpha.13": + version: 12.0.0-alpha.13 + resolution: "@rescript/darwin-arm64@npm:12.0.0-alpha.13" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rescript/darwin-x64@npm:12.0.0-alpha.13": + version: 12.0.0-alpha.13 + resolution: "@rescript/darwin-x64@npm:12.0.0-alpha.13" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rescript/linux-arm64@npm:12.0.0-alpha.13": + version: 12.0.0-alpha.13 + resolution: "@rescript/linux-arm64@npm:12.0.0-alpha.13" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@rescript/linux-x64@npm:12.0.0-alpha.13": + version: 12.0.0-alpha.13 + resolution: "@rescript/linux-x64@npm:12.0.0-alpha.13" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@rescript/win32-x64@npm:12.0.0-alpha.13": + version: 12.0.0-alpha.13 + resolution: "@rescript/win32-x64@npm:12.0.0-alpha.13" + conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -39,8 +65,6 @@ __metadata: "@testrepo/namespace-casing@workspace:packages/namespace-casing": version: 0.0.0-use.local resolution: "@testrepo/namespace-casing@workspace:packages/namespace-casing" - dependencies: - rescript: "npm:*" languageName: unknown linkType: soft @@ -53,19 +77,36 @@ __metadata: "@testrepo/with-dev-deps@workspace:packages/with-dev-deps": version: 0.0.0-use.local resolution: "@testrepo/with-dev-deps@workspace:packages/with-dev-deps" - dependencies: - "@rescript/core": "npm:*" languageName: unknown linkType: soft -"rescript@npm:*, rescript@npm:11.1.4": - version: 11.1.4 - resolution: "rescript@npm:11.1.4" +"rescript@npm:12.0.0-alpha.13": + version: 12.0.0-alpha.13 + resolution: "rescript@npm:12.0.0-alpha.13" + dependencies: + "@rescript/darwin-arm64": "npm:12.0.0-alpha.13" + "@rescript/darwin-x64": "npm:12.0.0-alpha.13" + "@rescript/linux-arm64": "npm:12.0.0-alpha.13" + "@rescript/linux-x64": "npm:12.0.0-alpha.13" + "@rescript/win32-x64": "npm:12.0.0-alpha.13" + dependenciesMeta: + "@rescript/darwin-arm64": + optional: true + "@rescript/darwin-x64": + optional: true + "@rescript/linux-arm64": + optional: true + "@rescript/linux-x64": + optional: true + "@rescript/win32-x64": + optional: true bin: - bsc: bsc - bstracing: lib/bstracing - rescript: rescript - checksum: 10c0/7f12186a84209f586457a60e65755fcdfbcbb503ac805c60ab5132ce4c927bc264c3b851419ed5498ba7dc4066723377bb7453f893f482a0ccd424986d02beba + bsc: cli/bsc.js + bstracing: cli/bstracing.js + rescript: cli/rescript.js + rescript-tools: cli/rescript-tools.js + rewatch: cli/rewatch.js + checksum: 10c0/97ad8615b1a1cbace61e050b65a10bbe280c77b4fe693b99aee7d3c6bb0b2ebd284db16a7aec3d65179267dc610a9737fdf74197f6322b1281113fd588fe1b32 languageName: node linkType: hard @@ -73,6 +114,6 @@ __metadata: version: 0.0.0-use.local resolution: "testrepo@workspace:." dependencies: - rescript: "npm:11.1.4" + rescript: "npm:12.0.0-alpha.13" languageName: unknown linkType: soft diff --git a/rewatch/tests/compile.sh b/rewatch/tests/compile.sh index dd39bf5b96..fd035bf902 100755 --- a/rewatch/tests/compile.sh +++ b/rewatch/tests/compile.sh @@ -39,32 +39,36 @@ mv ./packages/main/src/Main2.res ./packages/main/src/Main.res # Rename a file with a dependent - this should trigger an error mv ./packages/main/src/InternalDep.res ./packages/main/src/InternalDep2.res rewatch build &> ../tests/snapshots/rename-file-internal-dep.txt -# replace the absolute path so the snapshot is the same on all machines -replace "s/$(pwd | sed "s/\//\\\\\//g")//g" ../tests/snapshots/rename-file-internal-dep.txt +# normalize paths so the snapshot is the same on all machines +normalize_paths ../tests/snapshots/rename-file-internal-dep.txt mv ./packages/main/src/InternalDep2.res ./packages/main/src/InternalDep.res # Rename a file with a dependent in a namespaced package - this should trigger an error (regression) mv ./packages/new-namespace/src/Other_module.res ./packages/new-namespace/src/Other_module2.res rewatch build &> ../tests/snapshots/rename-file-internal-dep-namespace.txt -# replace the absolute path so the snapshot is the same on all machines -replace "s/$(pwd | sed "s/\//\\\\\//g")//g" ../tests/snapshots/rename-file-internal-dep-namespace.txt +# normalize paths so the snapshot is the same on all machines +normalize_paths ../tests/snapshots/rename-file-internal-dep-namespace.txt mv ./packages/new-namespace/src/Other_module2.res ./packages/new-namespace/src/Other_module.res rewatch build &> /dev/null mv ./packages/main/src/ModuleWithInterface.resi ./packages/main/src/ModuleWithInterface2.resi rewatch build &> ../tests/snapshots/rename-interface-file.txt +# normalize paths so the snapshot is the same on all machines +normalize_paths ../tests/snapshots/rename-interface-file.txt mv ./packages/main/src/ModuleWithInterface2.resi ./packages/main/src/ModuleWithInterface.resi rewatch build &> /dev/null mv ./packages/main/src/ModuleWithInterface.res ./packages/main/src/ModuleWithInterface2.res rewatch build &> ../tests/snapshots/rename-file-with-interface.txt +# normalize paths so the snapshot is the same on all machines +normalize_paths ../tests/snapshots/rename-file-with-interface.txt mv ./packages/main/src/ModuleWithInterface2.res ./packages/main/src/ModuleWithInterface.res rewatch build &> /dev/null # when deleting a file that other files depend on, the compile should fail rm packages/dep02/src/Dep02.res rewatch build &> ../tests/snapshots/remove-file.txt -# replace the absolute path so the snapshot is the same on all machines -replace "s/$(pwd | sed "s/\//\\\\\//g")//g" ../tests/snapshots/remove-file.txt +# normalize paths so the snapshot is the same on all machines +normalize_paths ../tests/snapshots/remove-file.txt git checkout -- packages/dep02/src/Dep02.res rewatch build &> /dev/null diff --git a/rewatch/tests/lock.sh b/rewatch/tests/lock.sh index 4522a7a037..f96d68db3e 100755 --- a/rewatch/tests/lock.sh +++ b/rewatch/tests/lock.sh @@ -14,21 +14,20 @@ else fi exit_watcher() { - # Try to find child process, if not found just kill the process directly + # kill watcher by removing lock file rm lib/rewatch.lock } rewatch_bg watch > /dev/null 2>&1 & +success "Watcher Started" -sleep 1 +sleep 2 -if rewatch watch | grep 'Could not start Rewatch:' &> /dev/null; +if rewatch build | grep 'Could not start Rewatch:' &> /dev/null; then - # rm output.txt success "Lock is correctly set" exit_watcher else - # rm output.txt error "Not setting lock correctly" exit_watcher exit 1 @@ -40,7 +39,7 @@ touch tmp.txt rewatch_bg watch > tmp.txt 2>&1 & success "Watcher Started" -sleep 1 +sleep 2 if cat tmp.txt | grep 'Could not start Rewatch:' &> /dev/null; then diff --git a/rewatch/tests/snapshots/dependency-cycle.txt b/rewatch/tests/snapshots/dependency-cycle.txt index 6c5236a57d..c949d4005c 100644 --- a/rewatch/tests/snapshots/dependency-cycle.txt +++ b/rewatch/tests/snapshots/dependency-cycle.txt @@ -1,10 +1,6 @@ -[1/7] ๐Ÿ“ฆ Building package tree... [1/7] ๐Ÿ“ฆ Built package tree in 0.00s -[2/7] ๐Ÿ‘€ Finding source files... [2/7] ๐Ÿ‘€ Found source files in 0.00s -[3/7] ๐Ÿ“ Reading compile state... [3/7] ๐Ÿ“ Read compile state 0.00s -[4/7] ๐Ÿงน Cleaning up previous build... [4/7] ๐Ÿงน Cleaned 0/94 0.00s - [5/7] ๐Ÿงฑ Parsed 1 source files in 0.00s - [6/7] ๐ŸŒด Collected deps in 0.00s - [7/7] โŒ Compiled 0 modules in 0.00s +Cleaned 0/14 +Parsed 1 source files +Compiled 0 modules Can't continue... Found a circular dependency in your code: Dep01 @@ -13,4 +9,4 @@ Dep01 โ†’ NewNamespace.NS_alias โ†’ Dep01 -Incremental build failed. Error:  โŒ Failed to Compile. See Errors Above +Incremental build failed. Error:  Failed to Compile. See Errors Above diff --git a/rewatch/tests/snapshots/remove-file.txt b/rewatch/tests/snapshots/remove-file.txt index 7fdacf29a3..921f9d4246 100644 --- a/rewatch/tests/snapshots/remove-file.txt +++ b/rewatch/tests/snapshots/remove-file.txt @@ -1,10 +1,6 @@ -[1/7] ๐Ÿ“ฆ Building package tree... [1/7] ๐Ÿ“ฆ Built package tree in 0.00s -[2/7] ๐Ÿ‘€ Finding source files... [2/7] ๐Ÿ‘€ Found source files in 0.00s -[3/7] ๐Ÿ“ Reading compile state... [3/7] ๐Ÿ“ Read compile state 0.00s -[4/7] ๐Ÿงน Cleaning up previous build... [4/7] ๐Ÿงน Cleaned 1/94 0.00s - [5/7] ๐Ÿงฑ Parsed 0 source files in 0.00s - [6/7] ๐ŸŒด Collected deps in 0.00s - [7/7] โŒ Compiled 1 modules in 0.00s +Cleaned 1/14 +Parsed 0 source files +Compiled 1 modules We've found a bug for you! /packages/dep01/src/Dep01.res:3:9-17 @@ -22,4 +18,4 @@ -Incremental build failed. Error:  โŒ Failed to Compile. See Errors Above +Incremental build failed. Error:  Failed to Compile. See Errors Above diff --git a/rewatch/tests/snapshots/rename-file-internal-dep-namespace.txt b/rewatch/tests/snapshots/rename-file-internal-dep-namespace.txt index a5836f99fa..1950bad9e3 100644 --- a/rewatch/tests/snapshots/rename-file-internal-dep-namespace.txt +++ b/rewatch/tests/snapshots/rename-file-internal-dep-namespace.txt @@ -1,10 +1,6 @@ -[1/7] ๐Ÿ“ฆ Building package tree... [1/7] ๐Ÿ“ฆ Built package tree in 0.00s -[2/7] ๐Ÿ‘€ Finding source files... [2/7] ๐Ÿ‘€ Found source files in 0.00s -[3/7] ๐Ÿ“ Reading compile state... [3/7] ๐Ÿ“ Read compile state 0.00s -[4/7] ๐Ÿงน Cleaning up previous build... [4/7] ๐Ÿงน Cleaned 2/94 0.00s - [5/7] ๐Ÿงฑ Parsed 2 source files in 0.00s - [6/7] ๐ŸŒด Collected deps in 0.00s - [7/7] โŒ Compiled 3 modules in 0.00s +Cleaned 2/14 +Parsed 2 source files +Compiled 3 modules We've found a bug for you! /packages/new-namespace/src/NS_alias.res:2:1-16 @@ -22,4 +18,4 @@ Hint: Did you mean Other_module2? -Incremental build failed. Error:  โŒ Failed to Compile. See Errors Above +Incremental build failed. Error:  Failed to Compile. See Errors Above diff --git a/rewatch/tests/snapshots/rename-file-internal-dep.txt b/rewatch/tests/snapshots/rename-file-internal-dep.txt index 9dc7b4a390..f81a6dece4 100644 --- a/rewatch/tests/snapshots/rename-file-internal-dep.txt +++ b/rewatch/tests/snapshots/rename-file-internal-dep.txt @@ -1,10 +1,6 @@ -[1/7] ๐Ÿ“ฆ Building package tree... [1/7] ๐Ÿ“ฆ Built package tree in 0.00s -[2/7] ๐Ÿ‘€ Finding source files... [2/7] ๐Ÿ‘€ Found source files in 0.00s -[3/7] ๐Ÿ“ Reading compile state... [3/7] ๐Ÿ“ Read compile state 0.00s -[4/7] ๐Ÿงน Cleaning up previous build... [4/7] ๐Ÿงน Cleaned 2/94 0.00s - [5/7] ๐Ÿงฑ Parsed 2 source files in 0.00s - [6/7] ๐ŸŒด Collected deps in 0.00s - [7/7] โŒ Compiled 2 modules in 0.00s +Cleaned 2/14 +Parsed 2 source files +Compiled 2 modules We've found a bug for you! /packages/main/src/Main.res:4:8-24 @@ -22,4 +18,4 @@ -Incremental build failed. Error:  โŒ Failed to Compile. See Errors Above +Incremental build failed. Error:  Failed to Compile. See Errors Above diff --git a/rewatch/tests/snapshots/rename-file-with-interface.txt b/rewatch/tests/snapshots/rename-file-with-interface.txt index 45d05ea95f..257fba3570 100644 --- a/rewatch/tests/snapshots/rename-file-with-interface.txt +++ b/rewatch/tests/snapshots/rename-file-with-interface.txt @@ -1,11 +1,4 @@ -[1/7] ๐Ÿ“ฆ Building package tree... [1/7] ๐Ÿ“ฆ Built package tree in 0.00s -[2/7] ๐Ÿ‘€ Finding source files...WARN:  No implementation file found for interface file (skipping): src/ModuleWithInterface.resi - [2/7] ๐Ÿ‘€ Found source files in 0.00s -[3/7] ๐Ÿ“ Reading compile state... [3/7] ๐Ÿ“ Read compile state 0.00s -[4/7] ๐Ÿงน Cleaning up previous build... [4/7] ๐Ÿงน Cleaned 2/94 0.00s - [5/7] ๐Ÿงฑ Parsed 1 source files in 0.00s - [6/7] ๐ŸŒด Collected deps in 0.00s - [7/7] ๐Ÿคบ Compiled 2 modules in 0.00s - - โœจ Finished Compilation in 0.00s +Cleaned 2/14 +Parsed 1 source files +Compiled 2 modules diff --git a/rewatch/tests/snapshots/rename-file.txt b/rewatch/tests/snapshots/rename-file.txt index 7965bd423b..5b20d86fd7 100644 --- a/rewatch/tests/snapshots/rename-file.txt +++ b/rewatch/tests/snapshots/rename-file.txt @@ -1,9 +1,3 @@ -[1/7] ๐Ÿ“ฆ Building package tree... [1/7] ๐Ÿ“ฆ Built package tree in 0.00s -[2/7] ๐Ÿ‘€ Finding source files... [2/7] ๐Ÿ‘€ Found source files in 0.00s -[3/7] ๐Ÿ“ Reading compile state... [3/7] ๐Ÿ“ Read compile state 0.00s -[4/7] ๐Ÿงน Cleaning up previous build... [4/7] ๐Ÿงน Cleaned 1/94 0.00s - [5/7] ๐Ÿงฑ Parsed 1 source files in 0.00s - [6/7] ๐ŸŒด Collected deps in 0.00s - [7/7] ๐Ÿคบ Compiled 1 modules in 0.00s - - โœจ Finished Compilation in 0.00s +Cleaned 1/14 +Parsed 1 source files +Compiled 1 modules diff --git a/rewatch/tests/snapshots/rename-interface-file.txt b/rewatch/tests/snapshots/rename-interface-file.txt index be2cd61a21..e2d7fd9752 100644 --- a/rewatch/tests/snapshots/rename-interface-file.txt +++ b/rewatch/tests/snapshots/rename-interface-file.txt @@ -1,11 +1,4 @@ -[1/7] ๐Ÿ“ฆ Building package tree... [1/7] ๐Ÿ“ฆ Built package tree in 0.00s -[2/7] ๐Ÿ‘€ Finding source files...WARN:  No implementation file found for interface file (skipping): src/ModuleWithInterface2.resi - [2/7] ๐Ÿ‘€ Found source files in 0.00s -[3/7] ๐Ÿ“ Reading compile state... [3/7] ๐Ÿ“ Read compile state 0.00s -[4/7] ๐Ÿงน Cleaning up previous build... [4/7] ๐Ÿงน Cleaned 1/94 0.00s - [5/7] ๐Ÿงฑ Parsed 1 source files in 0.00s - [6/7] ๐ŸŒด Collected deps in 0.00s - [7/7] ๐Ÿคบ Compiled 2 modules in 0.00s - - โœจ Finished Compilation in 0.00s +Cleaned 1/14 +Parsed 1 source files +Compiled 2 modules diff --git a/rewatch/tests/suite-ci.sh b/rewatch/tests/suite-ci.sh index 60970745e1..87fd645cb5 100755 --- a/rewatch/tests/suite-ci.sh +++ b/rewatch/tests/suite-ci.sh @@ -6,7 +6,7 @@ cd $(dirname $0) if [ -n "$1" ]; then REWATCH_EXECUTABLE="$1" else - REWATCH_EXECUTABLE="../target/release/rewatch" + REWATCH_EXECUTABLE="../target/release/rewatch --bsc-path ../../_build/install/default/bin/bsc" fi export REWATCH_EXECUTABLE diff --git a/rewatch/tests/utils.sh b/rewatch/tests/utils.sh index f2ee211ddd..1dffbe8a8d 100644 --- a/rewatch/tests/utils.sh +++ b/rewatch/tests/utils.sh @@ -3,8 +3,44 @@ overwrite() { echo -e "\r\033[1A\033[0K$@"; } success() { echo -e "- โœ… \033[32m$1\033[0m"; } error() { echo -e "- ๐Ÿ›‘ \033[31m$1\033[0m"; } bold() { echo -e "\033[1m$1\033[0m"; } -rewatch() { RUST_BACKTRACE=1 $REWATCH_EXECUTABLE --no-timing=true $@; } -rewatch_bg() { RUST_BACKTRACE=1 nohup $REWATCH_EXECUTABLE --no-timing=true $@; } +rewatch() { RUST_BACKTRACE=1 $REWATCH_EXECUTABLE --no-timing=true --snapshot-output=true $@; } +rewatch_bg() { RUST_BACKTRACE=1 nohup $REWATCH_EXECUTABLE --no-timing=true --snapshot-output=true $@; } + +# Detect if running on Windows +is_windows() { + [[ $OSTYPE == 'msys'* || $OSTYPE == 'cygwin'* || $OSTYPE == 'win'* ]]; +} + +# get pwd with forward slashes +pwd_prefix() { + if is_windows; then + # On Windows, escape backslashes for sed and convert to forward slashes for consistent snapshots + # This ensures paths like C:\a\b are replaced correctly + # First get the Windows-style path with backslashes + local win_path=$(pwd -W | sed "s#/#\\\\#g") + # Then escape the backslashes for sed replacement + echo $win_path | sed 's#\\#\\\\#g' + else + # On Unix-like systems, escape forward slashes for sed + echo $(pwd | sed "s#/#\\/#g") + fi +} + +# replace the absolute path so the snapshot is the same on all machines +# then normalize the path separators +normalize_paths() { + if [[ $OSTYPE == 'darwin'* ]]; + then + sed -i '' "s#$(pwd_prefix)##g" $1; + else + if is_windows; then + sed -i "s#$(pwd_prefix)##g" $1 + sed -i "s#\\\\#/#g" $1 + else + sed -i "s#$(pwd_prefix)##g" $1; + fi + fi +} replace() { if [[ $OSTYPE == 'darwin'* ]]; diff --git a/rewatch/tests/watch.sh b/rewatch/tests/watch.sh index e3a24c0997..83cee30f1b 100755 --- a/rewatch/tests/watch.sh +++ b/rewatch/tests/watch.sh @@ -12,7 +12,7 @@ else fi exit_watcher() { - # we need to kill the parent process (rewatch) + # kill watcher by removing lock file rm lib/rewatch.lock } @@ -21,7 +21,7 @@ success "Watcher Started" echo 'Js.log("added-by-test")' >> ./packages/main/src/Main.res -sleep 1 +sleep 2 if node ./packages/main/src/Main.mjs | grep 'added-by-test' &> /dev/null; then diff --git a/scripts/buildRuntimeRewatch.sh b/scripts/buildRuntimeRewatch.sh index bb8d97fc56..2d2bd014af 100755 --- a/scripts/buildRuntimeRewatch.sh +++ b/scripts/buildRuntimeRewatch.sh @@ -11,3 +11,6 @@ cp runtime/lib/es6/*.js lib/es6 cp runtime/lib/js/*.js lib/js cp runtime/lib/bs/*.@(cmi|cmj|cmt|cmti) lib/ocaml/ cp runtime/*.@(res|resi) lib/ocaml/ +# overwrite the stdlib build artifacts to the testrepo +mkdir -p rewatch/testrepo/node_modules/rescript/lib/ocaml +cp -rf lib/ocaml rewatch/testrepo/node_modules/rescript/lib/ocaml \ No newline at end of file