Skip to content

Commit 061d069

Browse files
committed
⚡ - efficient incremental compile
1 parent 1535e29 commit 061d069

File tree

11 files changed

+494
-225
lines changed

11 files changed

+494
-225
lines changed

src/build.rs

Lines changed: 115 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,21 @@ pub mod packages;
88
pub mod parse;
99
pub mod read_compile_state;
1010

11+
use crate::build::compile::{mark_modules_with_deleted_deps_dirty, mark_modules_with_expired_deps_dirty};
1112
use crate::helpers::emojis::*;
1213
use crate::helpers::{self, get_workspace_root};
14+
use ahash::AHashSet;
1315
use build_types::*;
1416
use console::style;
1517
use indicatif::{ProgressBar, ProgressStyle};
1618
use serde::Serialize;
1719
use std::io::{stdout, Write};
1820
use std::path::PathBuf;
19-
use std::process::Command;
20-
use std::time::Instant;
21+
use std::time::{Duration, Instant};
2122

2223
use self::compile::compiler_args;
2324
use self::parse::parser_args;
2425

25-
pub fn get_version(bsc_path: &str) -> String {
26-
let version_cmd = Command::new(bsc_path)
27-
.args(["-v"])
28-
.output()
29-
.expect("failed to find version");
30-
31-
std::str::from_utf8(&version_cmd.stdout)
32-
.expect("Could not read version from rescript")
33-
.replace("\n", "")
34-
.replace("ReScript ", "")
35-
}
36-
3726
fn is_dirty(module: &Module) -> bool {
3827
match module.source_type {
3928
SourceType::SourceFile(SourceFile {
@@ -45,7 +34,7 @@ fn is_dirty(module: &Module) -> bool {
4534
..
4635
}) => true,
4736
SourceType::SourceFile(_) => false,
48-
SourceType::MlMap(MlMap { dirty, .. }) => module.compile_dirty || dirty,
37+
SourceType::MlMap(MlMap { dirty, .. }) => dirty,
4938
}
5039
}
5140

@@ -65,11 +54,11 @@ pub fn get_compiler_args(path: &str) -> String {
6554
packages::get_package_name(&workspace_root.to_owned().unwrap_or(package_root.to_owned()));
6655
let package_name = packages::get_package_name(&package_root);
6756
let bsc_path = helpers::get_bsc(&package_root, workspace_root.to_owned());
68-
let rescript_version = get_version(&bsc_path);
57+
let rescript_version = helpers::get_rescript_version(&bsc_path);
6958
let packages = packages::make(
7059
&None,
7160
&workspace_root.to_owned().unwrap_or(package_root.to_owned()),
72-
workspace_root.to_owned(),
61+
&workspace_root,
7362
);
7463
// make PathBuf from package root and get the relative path for filename
7564
let relative_filename = PathBuf::from(&filename)
@@ -111,18 +100,16 @@ pub fn get_compiler_args(path: &str) -> String {
111100
.unwrap()
112101
}
113102

114-
pub fn build(filter: &Option<regex::Regex>, path: &str, no_timing: bool) -> Result<BuildState, ()> {
115-
let timing_total = Instant::now();
103+
pub fn initialize_build<'a>(
104+
default_timing: Option<Duration>,
105+
filter: &Option<regex::Regex>,
106+
path: &str,
107+
) -> Result<BuildState, ()> {
116108
let project_root = helpers::get_abs_path(path);
117109
let workspace_root = helpers::get_workspace_root(&project_root);
118110
let bsc_path = helpers::get_bsc(&project_root, workspace_root.to_owned());
119111
let root_config_name = packages::get_package_name(&project_root);
120-
let rescript_version = get_version(&bsc_path);
121-
let default_timing: Option<std::time::Duration> = if no_timing {
122-
Some(std::time::Duration::new(0.0 as u64, 0.0 as u32))
123-
} else {
124-
None
125-
};
112+
let rescript_version = helpers::get_rescript_version(&bsc_path);
126113

127114
print!(
128115
"{} {} Building package tree...",
@@ -131,7 +118,7 @@ pub fn build(filter: &Option<regex::Regex>, path: &str, no_timing: bool) -> Resu
131118
);
132119
let _ = stdout().flush();
133120
let timing_package_tree = Instant::now();
134-
let packages = packages::make(&filter, &project_root, workspace_root.to_owned());
121+
let packages = packages::make(&filter, &project_root, &workspace_root);
135122
let timing_package_tree_elapsed = timing_package_tree.elapsed();
136123

137124
println!(
@@ -156,9 +143,15 @@ pub fn build(filter: &Option<regex::Regex>, path: &str, no_timing: bool) -> Resu
156143
LOOKING_GLASS
157144
);
158145
let _ = stdout().flush();
159-
let mut build_state = BuildState::new(project_root, root_config_name, packages);
146+
let mut build_state = BuildState::new(
147+
project_root,
148+
root_config_name,
149+
packages,
150+
workspace_root,
151+
rescript_version,
152+
bsc_path,
153+
);
160154
packages::parse_packages(&mut build_state);
161-
logs::initialize(&build_state.packages);
162155
let timing_source_files_elapsed = timing_source_files.elapsed();
163156
println!(
164157
"{}\r{} {}Found source files in {:.2}s",
@@ -171,53 +164,72 @@ pub fn build(filter: &Option<regex::Regex>, path: &str, no_timing: bool) -> Resu
171164
);
172165

173166
print!(
174-
"{} {} Cleaning up previous build...",
167+
"{} {} Reading compile state...",
175168
style("[3/7]").bold().dim(),
169+
LOOKING_GLASS
170+
);
171+
let _ = stdout().flush();
172+
let timing_compile_state = Instant::now();
173+
let compile_assets_state = read_compile_state::read(&mut build_state);
174+
let timing_compile_state_elapsed = timing_compile_state.elapsed();
175+
println!(
176+
"{}\r{} {}Read compile state {:.2}s",
177+
LINE_CLEAR,
178+
style("[3/7]").bold().dim(),
179+
CHECKMARK,
180+
default_timing
181+
.unwrap_or(timing_compile_state_elapsed)
182+
.as_secs_f64()
183+
);
184+
185+
print!(
186+
"{} {} Cleaning up previous build...",
187+
style("[4/7]").bold().dim(),
176188
SWEEP
177189
);
178190
let timing_cleanup = Instant::now();
179-
let compile_assets_state = read_compile_state::read(&mut build_state);
180-
let (diff_cleanup, total_cleanup, deleted_module_names) =
181-
clean::cleanup_previous_build(&mut build_state, compile_assets_state);
191+
let (diff_cleanup, total_cleanup) = clean::cleanup_previous_build(&mut build_state, compile_assets_state);
182192
let timing_cleanup_elapsed = timing_cleanup.elapsed();
183193
println!(
184194
"{}\r{} {}Cleaned {}/{} {:.2}s",
185195
LINE_CLEAR,
186-
style("[3/7]").bold().dim(),
196+
style("[4/7]").bold().dim(),
187197
CHECKMARK,
188198
diff_cleanup,
189199
total_cleanup,
190200
default_timing.unwrap_or(timing_cleanup_elapsed).as_secs_f64()
191201
);
202+
Ok(build_state)
203+
}
192204

205+
pub fn incremental_build(
206+
build_state: &mut BuildState,
207+
default_timing: Option<Duration>,
208+
initial_build: bool,
209+
) -> Result<(), ()> {
210+
logs::initialize(&build_state.packages);
193211
let num_dirty_modules = build_state.modules.values().filter(|m| is_dirty(m)).count() as u64;
194212

195213
let pb = ProgressBar::new(num_dirty_modules);
196214
pb.set_style(
197215
ProgressStyle::with_template(&format!(
198216
"{} {} Parsing... {{spinner}} {{pos}}/{{len}} {{msg}}",
199-
style("[4/7]").bold().dim(),
217+
style("[5/7]").bold().dim(),
200218
CODE
201219
))
202220
.unwrap(),
203221
);
204222

205223
let timing_ast = Instant::now();
206-
let result_asts = parse::generate_asts(
207-
&rescript_version,
208-
&mut build_state,
209-
|| pb.inc(1),
210-
&bsc_path,
211-
&workspace_root,
212-
);
224+
let result_asts = parse::generate_asts(build_state, || pb.inc(1));
213225
let timing_ast_elapsed = timing_ast.elapsed();
214226

215227
match result_asts {
216228
Ok(err) => {
217229
println!(
218230
"{}\r{} {}Parsed {} source files in {:.2}s",
219231
LINE_CLEAR,
220-
style("[4/7]").bold().dim(),
232+
style("[5/7]").bold().dim(),
221233
CHECKMARK,
222234
num_dirty_modules,
223235
default_timing.unwrap_or(timing_ast_elapsed).as_secs_f64()
@@ -229,87 +241,121 @@ pub fn build(filter: &Option<regex::Regex>, path: &str, no_timing: bool) -> Resu
229241
println!(
230242
"{}\r{} {}Error parsing source files in {:.2}s",
231243
LINE_CLEAR,
232-
style("[4/7]").bold().dim(),
244+
style("[5/7]").bold().dim(),
233245
CROSS,
234246
default_timing.unwrap_or(timing_ast_elapsed).as_secs_f64()
235247
);
236248
print!("{}", &err);
237-
clean::cleanup_after_build(&build_state);
238249
return Err(());
239250
}
240251
}
241-
242252
let timing_deps = Instant::now();
243-
deps::get_deps(&mut build_state, &deleted_module_names);
253+
deps::get_deps(build_state, &build_state.deleted_modules.to_owned());
244254
let timing_deps_elapsed = timing_deps.elapsed();
245255

246256
println!(
247257
"{}\r{} {}Collected deps in {:.2}s",
248258
LINE_CLEAR,
249-
style("[5/7]").bold().dim(),
259+
style("[6/7]").bold().dim(),
250260
CHECKMARK,
251261
default_timing.unwrap_or(timing_deps_elapsed).as_secs_f64()
252262
);
253263

264+
// track the compile dirty state, we reset it when the compile fails
265+
let mut tracked_dirty_modules = AHashSet::new();
266+
for (module_name, module) in build_state.modules.iter() {
267+
if module.compile_dirty {
268+
tracked_dirty_modules.insert(module_name.to_owned());
269+
}
270+
}
271+
if initial_build {
272+
// repair broken state
273+
mark_modules_with_expired_deps_dirty(build_state);
274+
}
275+
mark_modules_with_deleted_deps_dirty(build_state);
276+
277+
// print all the compile_dirty modules
278+
// for (module_name, module) in build_state.modules.iter() {
279+
// if module.compile_dirty {
280+
// println!("compile dirty: {}", module_name);
281+
// }
282+
// }
283+
254284
let start_compiling = Instant::now();
255285
let pb = ProgressBar::new(build_state.modules.len().try_into().unwrap());
256286
pb.set_style(
257287
ProgressStyle::with_template(&format!(
258288
"{} {} Compiling... {{spinner}} {{pos}}/{{len}} {{msg}}",
259-
style("[6/7]").bold().dim(),
289+
style("[7/7]").bold().dim(),
260290
SWORDS
261291
))
262292
.unwrap(),
263293
);
264-
let (compile_errors, compile_warnings, num_compiled_modules) = compile::compile(
265-
&mut build_state,
266-
&deleted_module_names,
267-
&rescript_version,
268-
|| pb.inc(1),
269-
|size| pb.set_length(size),
270-
&bsc_path,
271-
);
294+
let (compile_errors, compile_warnings, num_compiled_modules) =
295+
compile::compile(build_state, || pb.inc(1), |size| pb.set_length(size));
272296
let compile_duration = start_compiling.elapsed();
273297

274298
logs::finalize(&build_state.packages);
275299
pb.finish();
276-
clean::cleanup_after_build(&build_state);
277300
if compile_errors.len() > 0 {
278301
if helpers::contains_ascii_characters(&compile_warnings) {
279302
println!("{}", &compile_warnings);
280303
}
281304
println!(
282305
"{}\r{} {}Compiled {} modules in {:.2}s",
283306
LINE_CLEAR,
284-
style("[6/7]").bold().dim(),
307+
style("[7/7]").bold().dim(),
285308
CROSS,
286309
num_compiled_modules,
287310
default_timing.unwrap_or(compile_duration).as_secs_f64()
288311
);
289312
print!("{}", &compile_errors);
313+
// mark the original files as dirty again, because we didn't complete a full build
314+
for (module_name, module) in build_state.modules.iter_mut() {
315+
if tracked_dirty_modules.contains(module_name) {
316+
module.compile_dirty = true;
317+
}
318+
}
290319
return Err(());
291320
} else {
292321
println!(
293322
"{}\r{} {}Compiled {} modules in {:.2}s",
294323
LINE_CLEAR,
295-
style("[6/7]").bold().dim(),
324+
style("[7/7]").bold().dim(),
296325
CHECKMARK,
297326
num_compiled_modules,
298327
default_timing.unwrap_or(compile_duration).as_secs_f64()
299328
);
300329
if helpers::contains_ascii_characters(&compile_warnings) {
301330
print!("{}", &compile_warnings);
302331
}
332+
Ok(())
303333
}
334+
}
304335

305-
let timing_total_elapsed = timing_total.elapsed();
306-
println!(
307-
"{}\r{} {}Finished Compilation in {:.2}s",
308-
LINE_CLEAR,
309-
style("[7/7]").bold().dim(),
310-
CHECKMARK,
311-
default_timing.unwrap_or(timing_total_elapsed).as_secs_f64()
312-
);
313-
314-
Ok(build_state)
336+
pub fn build(filter: &Option<regex::Regex>, path: &str, no_timing: bool) -> Result<BuildState, ()> {
337+
let default_timing: Option<std::time::Duration> = if no_timing {
338+
Some(std::time::Duration::new(0.0 as u64, 0.0 as u32))
339+
} else {
340+
None
341+
};
342+
let timing_total = Instant::now();
343+
let mut build_state = initialize_build(default_timing, filter, path)?;
344+
match incremental_build(&mut build_state, default_timing, true) {
345+
Ok(_) => {
346+
let timing_total_elapsed = timing_total.elapsed();
347+
println!(
348+
"{}\r{}Finished Compilation in {:.2}s",
349+
LINE_CLEAR,
350+
CHECKMARK,
351+
default_timing.unwrap_or(timing_total_elapsed).as_secs_f64()
352+
);
353+
clean::cleanup_after_build(&build_state);
354+
Ok(build_state)
355+
}
356+
Err(_) => {
357+
clean::cleanup_after_build(&build_state);
358+
Err(())
359+
}
360+
}
315361
}

src/build/build_types.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ pub struct BuildState {
8585
pub module_names: AHashSet<String>,
8686
pub project_root: String,
8787
pub root_config_name: String,
88+
pub deleted_modules: AHashSet<String>,
89+
pub rescript_version: String,
90+
pub bsc_path: String,
91+
pub workspace_root: Option<String>,
92+
pub deps_initialized: bool,
8893
}
8994

9095
impl BuildState {
@@ -95,13 +100,25 @@ impl BuildState {
95100
pub fn get_module(&self, module_name: &str) -> Option<&Module> {
96101
self.modules.get(module_name)
97102
}
98-
pub fn new(project_root: String, root_config_name: String, packages: AHashMap<String, Package>) -> Self {
103+
pub fn new(
104+
project_root: String,
105+
root_config_name: String,
106+
packages: AHashMap<String, Package>,
107+
workspace_root: Option<String>,
108+
rescript_version: String,
109+
bsc_path: String,
110+
) -> Self {
99111
Self {
100112
module_names: AHashSet::new(),
101113
modules: AHashMap::new(),
102-
packages: packages,
103-
project_root: project_root,
104-
root_config_name: root_config_name,
114+
packages,
115+
project_root,
116+
root_config_name,
117+
deleted_modules: AHashSet::new(),
118+
workspace_root,
119+
rescript_version,
120+
bsc_path,
121+
deps_initialized: false,
105122
}
106123
}
107124
pub fn insert_module(&mut self, module_name: &str, module: Module) {

0 commit comments

Comments
 (0)