Skip to content

Commit db08f13

Browse files
authored
✨ - Incremental compilation support (#95)
1 parent 256fee0 commit db08f13

File tree

6 files changed

+276
-200
lines changed

6 files changed

+276
-200
lines changed

src/bsconfig.rs

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use crate::build::packages;
2+
use convert_case::{Case, Casing};
13
use serde::Deserialize;
24
use std::fs;
35
use std::path::{Path, PathBuf};
@@ -104,7 +106,7 @@ pub struct Reason {
104106

105107
#[derive(Deserialize, Debug, Clone)]
106108
#[serde(untagged)]
107-
pub enum Namespace {
109+
pub enum NamespaceConfig {
108110
Bool(bool),
109111
String(String),
110112
}
@@ -135,7 +137,7 @@ pub struct JsxSpecs {
135137
/// # bsconfig.json representation
136138
/// This is tricky, there is a lot of ambiguity. This is probably incomplete.
137139
#[derive(Deserialize, Debug, Clone)]
138-
pub struct T {
140+
pub struct Config {
139141
pub name: String,
140142
pub sources: OneOrMore<Source>,
141143
#[serde(rename = "package-specs")]
@@ -153,7 +155,7 @@ pub struct T {
153155
#[serde(rename = "bsc-flags")]
154156
pub bsc_flags: Option<Vec<OneOrMore<String>>>,
155157
pub reason: Option<Reason>,
156-
pub namespace: Option<Namespace>,
158+
pub namespace: Option<NamespaceConfig>,
157159
pub jsx: Option<JsxSpecs>,
158160
pub uncurried: Option<bool>,
159161
// this is a new feature of rewatch, and it's not part of the bsconfig.json spec
@@ -231,11 +233,116 @@ pub fn flatten_ppx_flags(
231233
}
232234

233235
/// Try to convert a bsconfig from a certain path to a bsconfig struct
234-
pub fn read(path: String) -> T {
236+
pub fn read(path: String) -> Config {
235237
fs::read_to_string(path.clone())
236238
.map_err(|e| format!("Could not read bsconfig. {path} - {e}"))
237239
.and_then(|x| {
238-
serde_json::from_str::<T>(&x).map_err(|e| format!("Could not parse bsconfig. {path} - {e}"))
240+
serde_json::from_str::<Config>(&x).map_err(|e| format!("Could not parse bsconfig. {path} - {e}"))
239241
})
240242
.expect("Errors reading bsconfig")
241243
}
244+
245+
fn check_if_rescript11_or_higher(version: &str) -> bool {
246+
version.split(".").nth(0).unwrap().parse::<usize>().unwrap() >= 11
247+
}
248+
249+
fn namespace_from_package_name(package_name: &str) -> String {
250+
package_name
251+
.to_owned()
252+
.replace("@", "")
253+
.replace("/", "_")
254+
.to_case(Case::Pascal)
255+
}
256+
257+
impl Config {
258+
pub fn get_namespace(&self) -> packages::Namespace {
259+
let namespace_from_package = namespace_from_package_name(&self.name);
260+
match (self.namespace.as_ref(), self.namespace_entry.as_ref()) {
261+
(Some(NamespaceConfig::Bool(false)), _) => packages::Namespace::NoNamespace,
262+
(None, _) => packages::Namespace::NoNamespace,
263+
(Some(NamespaceConfig::Bool(true)), None) => {
264+
packages::Namespace::Namespace(namespace_from_package)
265+
}
266+
(Some(NamespaceConfig::Bool(true)), Some(entry)) => packages::Namespace::NamespaceWithEntry {
267+
namespace: namespace_from_package,
268+
entry: entry.to_string(),
269+
},
270+
(Some(NamespaceConfig::String(str)), None) => match str.as_str() {
271+
"true" => packages::Namespace::Namespace(namespace_from_package),
272+
namespace if namespace.is_case(Case::UpperFlat) => {
273+
packages::Namespace::Namespace(namespace.to_string())
274+
}
275+
namespace => packages::Namespace::Namespace(namespace.to_string().to_case(Case::Pascal)),
276+
},
277+
(Some(self::NamespaceConfig::String(str)), Some(entry)) => match str.as_str() {
278+
"true" => packages::Namespace::NamespaceWithEntry {
279+
namespace: namespace_from_package,
280+
entry: entry.to_string(),
281+
},
282+
namespace if namespace.is_case(Case::UpperFlat) => packages::Namespace::NamespaceWithEntry {
283+
namespace: namespace.to_string(),
284+
entry: entry.to_string(),
285+
},
286+
namespace => packages::Namespace::NamespaceWithEntry {
287+
namespace: namespace.to_string().to_case(Case::Pascal),
288+
entry: entry.to_string(),
289+
},
290+
},
291+
}
292+
}
293+
pub fn get_jsx_args(&self) -> Vec<String> {
294+
match (self.reason.to_owned(), self.jsx.to_owned()) {
295+
(_, Some(jsx)) => match jsx.version {
296+
Some(version) if version == 3 || version == 4 => {
297+
vec!["-bs-jsx".to_string(), version.to_string()]
298+
}
299+
Some(_version) => panic!("Unsupported JSX version"),
300+
None => vec![],
301+
},
302+
(Some(reason), None) => {
303+
vec!["-bs-jsx".to_string(), format!("{}", reason.react_jsx)]
304+
}
305+
_ => vec![],
306+
}
307+
}
308+
309+
pub fn get_jsx_mode_args(&self) -> Vec<String> {
310+
match self.jsx.to_owned() {
311+
Some(jsx) => match jsx.mode {
312+
Some(JsxMode::Classic) => {
313+
vec!["-bs-jsx-mode".to_string(), "classic".to_string()]
314+
}
315+
Some(JsxMode::Automatic) => {
316+
vec!["-bs-jsx-mode".to_string(), "automatic".to_string()]
317+
}
318+
319+
None => vec![],
320+
},
321+
_ => vec![],
322+
}
323+
}
324+
325+
pub fn get_jsx_module_args(&self) -> Vec<String> {
326+
match self.jsx.to_owned() {
327+
Some(jsx) => match jsx.module {
328+
Some(JsxModule::React) => {
329+
vec!["-bs-jsx-module".to_string(), "react".to_string()]
330+
}
331+
None => vec![],
332+
},
333+
_ => vec![],
334+
}
335+
}
336+
337+
pub fn get_uncurried_args(&self, version: &str) -> Vec<String> {
338+
if check_if_rescript11_or_higher(version) {
339+
match self.uncurried.to_owned() {
340+
// v11 is always uncurried except iff explicitly set to false in the root rescript.json
341+
Some(false) => vec![],
342+
_ => vec!["-uncurried".to_string()],
343+
}
344+
} else {
345+
vec![]
346+
}
347+
}
348+
}

src/build.rs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,36 +50,34 @@ pub struct CompilerArgs {
5050
pub parser_args: Vec<String>,
5151
}
5252

53-
pub fn get_compiler_args(path: &str) -> String {
53+
pub fn get_compiler_args(path: &str, rescript_version: Option<String>) -> String {
5454
let filename = &helpers::get_abs_path(path);
5555
let package_root = helpers::get_abs_path(
5656
&helpers::get_nearest_bsconfig(&std::path::PathBuf::from(path)).expect("Couldn't find package root"),
5757
);
5858
let workspace_root = get_workspace_root(&package_root).map(|p| helpers::get_abs_path(&p));
59-
let root_config_name =
60-
packages::get_package_name(&workspace_root.to_owned().unwrap_or(package_root.to_owned()));
61-
let package_name = packages::get_package_name(&package_root);
62-
let bsc_path = helpers::get_bsc(&package_root, workspace_root.to_owned());
63-
let rescript_version = helpers::get_rescript_version(&bsc_path);
64-
let packages = packages::make(
65-
&None,
66-
&workspace_root.to_owned().unwrap_or(package_root.to_owned()),
67-
&workspace_root,
68-
);
59+
let root_rescript_config =
60+
packages::read_bsconfig(&workspace_root.to_owned().unwrap_or(package_root.to_owned()));
61+
let rescript_config = packages::read_bsconfig(&package_root);
62+
let rescript_version = if let Some(rescript_version) = rescript_version {
63+
rescript_version
64+
} else {
65+
let bsc_path = helpers::get_bsc(&package_root, workspace_root.to_owned());
66+
helpers::get_rescript_version(&bsc_path)
67+
};
6968
// make PathBuf from package root and get the relative path for filename
7069
let relative_filename = PathBuf::from(&filename)
7170
.strip_prefix(PathBuf::from(&package_root).parent().unwrap())
7271
.unwrap()
7372
.to_string_lossy()
7473
.to_string();
75-
let root_package = packages.get(&root_config_name).unwrap();
76-
let package = packages.get(&package_name).unwrap();
7774
let (ast_path, parser_args) = parser_args(
78-
package,
79-
root_package,
75+
&rescript_config,
76+
&root_rescript_config,
8077
&relative_filename,
8178
&rescript_version,
8279
&workspace_root,
80+
workspace_root.as_ref().unwrap_or(&package_root),
8381
);
8482
let is_interface = filename.ends_with("i");
8583
let has_interface = if is_interface {
@@ -90,14 +88,16 @@ pub fn get_compiler_args(path: &str) -> String {
9088
PathBuf::from(&interface_filename).exists()
9189
};
9290
let compiler_args = compiler_args(
93-
package,
94-
root_package,
91+
&rescript_config,
92+
&root_rescript_config,
9593
&ast_path,
9694
&rescript_version,
9795
&relative_filename,
9896
is_interface,
9997
has_interface,
100-
&packages,
98+
&package_root,
99+
&workspace_root,
100+
&None,
101101
);
102102
serde_json::to_string_pretty(&CompilerArgs {
103103
compiler_args,

src/build/compile.rs

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ pub fn compile(
164164
true,
165165
&build_state.bsc_path,
166166
&build_state.packages,
167+
&build_state.project_root,
168+
&build_state.workspace_root,
167169
);
168170
Some(result)
169171
}
@@ -178,6 +180,8 @@ pub fn compile(
178180
false,
179181
&build_state.bsc_path,
180182
&build_state.packages,
183+
&build_state.project_root,
184+
&build_state.workspace_root,
181185
);
182186
// if let Err(error) = result.to_owned() {
183187
// println!("{}", error);
@@ -358,23 +362,22 @@ pub fn compile(
358362
}
359363

360364
pub fn compiler_args(
361-
package: &packages::Package,
362-
root_package: &packages::Package,
365+
config: &bsconfig::Config,
366+
root_config: &bsconfig::Config,
363367
ast_path: &str,
364368
version: &str,
365369
file_path: &str,
366370
is_interface: bool,
367371
has_interface: bool,
368-
packages: &AHashMap<String, packages::Package>,
372+
project_root: &str,
373+
workspace_root: &Option<String>,
374+
// if packages are known, we pass a reference here
375+
// this saves us a scan to find their paths
376+
packages: &Option<&AHashMap<String, packages::Package>>,
369377
) -> Vec<String> {
370-
let normal_deps = package
371-
.bsconfig
372-
.bs_dependencies
373-
.as_ref()
374-
.unwrap_or(&vec![])
375-
.to_owned();
376-
377-
let bsc_flags = bsconfig::flatten_flags(&package.bsconfig.bsc_flags);
378+
let normal_deps = config.bs_dependencies.as_ref().unwrap_or(&vec![]).to_owned();
379+
380+
let bsc_flags = bsconfig::flatten_flags(&config.bsc_flags);
378381
// don't compile dev-deps yet
379382
// let dev_deps = source
380383
// .package
@@ -386,24 +389,30 @@ pub fn compiler_args(
386389

387390
let deps = vec![normal_deps]
388391
.concat()
389-
.into_iter()
390-
.map(|x| {
391-
let package = &packages.get(&x).expect("expect package");
392-
vec![
393-
"-I".to_string(),
394-
helpers::canonicalize_string_path(&package.get_build_path()).unwrap(),
395-
]
392+
.par_iter()
393+
.map(|package_name| {
394+
let canonicalized_path = if let Some(packages) = packages {
395+
packages
396+
.get(package_name)
397+
.expect("expect package")
398+
.path
399+
.to_string()
400+
} else {
401+
packages::read_dependency(package_name, project_root, project_root, workspace_root)
402+
.expect("cannot find dep")
403+
};
404+
vec!["-I".to_string(), packages::get_build_path(&canonicalized_path)]
396405
})
397406
.collect::<Vec<Vec<String>>>();
398407

399-
let module_name = helpers::file_path_to_module_name(file_path, &package.namespace);
408+
let module_name = helpers::file_path_to_module_name(file_path, &config.get_namespace());
400409

401-
let namespace_args = match &package.namespace {
410+
let namespace_args = match &config.get_namespace() {
402411
packages::Namespace::NamespaceWithEntry { namespace: _, entry } if &module_name == entry => {
403412
// if the module is the entry we just want to open the namespace
404413
vec![
405414
"-open".to_string(),
406-
package.namespace.to_suffix().unwrap().to_string(),
415+
config.get_namespace().to_suffix().unwrap().to_string(),
407416
]
408417
}
409418
packages::Namespace::Namespace(_)
@@ -413,18 +422,18 @@ pub fn compiler_args(
413422
} => {
414423
vec![
415424
"-bs-ns".to_string(),
416-
package.namespace.to_suffix().unwrap().to_string(),
425+
config.get_namespace().to_suffix().unwrap().to_string(),
417426
]
418427
}
419428
packages::Namespace::NoNamespace => vec![],
420429
};
421430

422-
let jsx_args = root_package.get_jsx_args();
423-
let jsx_module_args = root_package.get_jsx_module_args();
424-
let jsx_mode_args = root_package.get_jsx_mode_args();
425-
let uncurried_args = package.get_uncurried_args(version, &root_package);
431+
let jsx_args = root_config.get_jsx_args();
432+
let jsx_module_args = root_config.get_jsx_module_args();
433+
let jsx_mode_args = root_config.get_jsx_mode_args();
434+
let uncurried_args = root_config.get_uncurried_args(version);
426435

427-
let warning_args: Vec<String> = match package.bsconfig.warnings.to_owned() {
436+
let warning_args: Vec<String> = match config.warnings.to_owned() {
428437
None => vec![],
429438
Some(warnings) => {
430439
let warn_number = match warnings.number {
@@ -466,14 +475,14 @@ pub fn compiler_args(
466475
debug!("Compiling file: {}", &module_name);
467476

468477
// TODO: Also read suffix from package-spec.
469-
let suffix = match root_package.bsconfig.suffix.to_owned() {
478+
let suffix = match root_config.suffix.to_owned() {
470479
Some(suffix) => suffix,
471480
None => String::from(bsconfig::DEFAULT_SUFFIX),
472481
};
473482

474483
vec![
475484
"-bs-package-name".to_string(),
476-
package.bsconfig.name.to_owned(),
485+
config.name.to_owned(),
477486
"-bs-package-output".to_string(),
478487
format!(
479488
"es6:{}:{}",
@@ -520,6 +529,8 @@ fn compile_file(
520529
is_interface: bool,
521530
bsc_path: &str,
522531
packages: &AHashMap<String, packages::Package>,
532+
project_root: &str,
533+
workspace_root: &Option<String>,
523534
) -> Result<Option<String>, String> {
524535
let build_path_abs = package.get_build_path();
525536
let implementation_file_path = match module.source_type {
@@ -529,14 +540,16 @@ fn compile_file(
529540
let module_name = helpers::file_path_to_module_name(implementation_file_path, &package.namespace);
530541
let has_interface = module.get_interface().is_some();
531542
let to_mjs_args = compiler_args(
532-
package,
533-
root_package,
543+
&package.bsconfig,
544+
&root_package.bsconfig,
534545
ast_path,
535546
version,
536547
&implementation_file_path,
537548
is_interface,
538549
has_interface,
539-
packages,
550+
project_root,
551+
workspace_root,
552+
&Some(packages),
540553
);
541554

542555
let to_mjs = Command::new(bsc_path)

0 commit comments

Comments
 (0)