Skip to content

Commit 9503d7b

Browse files
sverrejohfacebook-github-bot
authored andcommitted
Limit WalkDir to the root directories referenced by the configuration (#4850)
Summary: When not using Watchman the Relay Compiler will iterate over all files under `root_dir`, which can possibly include the entire repository. This is unnecessary work and might slow down large repositories. The watchman implementation only listens to files under the roots defined by the config, but this is not implemented for the directory walking implementation in `WalkDirFileSource`. This change moves the `get_all_roots()` functionality from `WatchmanFileSource` to `Config` and shares it with `WalkDirFileSource` to make it only iterate over the folders configured as targets in Relay. This fixes #4849 Pull Request resolved: #4850 Reviewed By: tyao1 Differential Revision: D66390485 Pulled By: captbaritone fbshipit-source-id: cdd6daa4368419dfcc012e50f8be45440f02cdcf
1 parent 28be5c2 commit 9503d7b

File tree

4 files changed

+144
-146
lines changed

4 files changed

+144
-146
lines changed

compiler/crates/relay-compiler/src/config.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,138 @@ impl Config {
612612
}
613613
}
614614
}
615+
616+
/// Compute all root paths that we need to query. All files relevant to the
617+
/// compiler should be in these directories.
618+
pub fn get_all_roots(&self) -> Vec<PathBuf> {
619+
let source_roots = self.get_source_roots();
620+
let extra_sources_roots = self.get_generated_sources_roots();
621+
let output_roots = self.get_output_dir_paths();
622+
let extension_roots = self.get_extension_roots();
623+
let schema_file_roots = self.get_schema_file_roots();
624+
let schema_dir_roots = self.get_schema_dir_paths();
625+
626+
unify_roots(
627+
source_roots
628+
.into_iter()
629+
.chain(extra_sources_roots)
630+
.chain(output_roots)
631+
.chain(extension_roots)
632+
.chain(schema_file_roots)
633+
.chain(schema_dir_roots)
634+
.collect(),
635+
)
636+
}
637+
638+
/// Returns all root directories of JS source files for the config.
639+
pub fn get_source_roots(&self) -> Vec<PathBuf> {
640+
self.sources.keys().cloned().collect()
641+
}
642+
643+
/// Returns all root directories of JS source files for the config.
644+
pub fn get_generated_sources_roots(&self) -> Vec<PathBuf> {
645+
self.generated_sources.keys().cloned().collect()
646+
}
647+
648+
/// Returns all root directories of GraphQL schema extension files for the
649+
/// config.
650+
pub fn get_extension_roots(&self) -> Vec<PathBuf> {
651+
self.projects
652+
.values()
653+
.flat_map(|project_config| project_config.schema_extensions.iter().cloned())
654+
.collect()
655+
}
656+
657+
/// Returns all output and extra artifact output directories for the config.
658+
pub fn get_output_dir_paths(&self) -> Vec<PathBuf> {
659+
let output_dirs = self
660+
.projects
661+
.values()
662+
.filter_map(|project_config| project_config.output.clone());
663+
664+
let extra_artifact_output_dirs = self
665+
.projects
666+
.values()
667+
.filter_map(|project_config| project_config.extra_artifacts_output.clone());
668+
669+
output_dirs.chain(extra_artifact_output_dirs).collect()
670+
}
671+
672+
/// Returns all paths that contain GraphQL schema files for the config.
673+
pub fn get_schema_file_paths(&self) -> Vec<PathBuf> {
674+
self.projects
675+
.values()
676+
.filter_map(|project_config| match &project_config.schema_location {
677+
SchemaLocation::File(schema_file) => Some(schema_file.clone()),
678+
SchemaLocation::Directory(_) => None,
679+
})
680+
.collect()
681+
}
682+
683+
/// Returns all GraphQL schema directories for the config.
684+
pub fn get_schema_dir_paths(&self) -> Vec<PathBuf> {
685+
self.projects
686+
.values()
687+
.filter_map(|project_config| match &project_config.schema_location {
688+
SchemaLocation::File(_) => None,
689+
SchemaLocation::Directory(schema_dir) => Some(schema_dir.clone()),
690+
})
691+
.collect()
692+
}
693+
694+
/// Returns root directories that contain GraphQL schema files.
695+
pub fn get_schema_file_roots(&self) -> impl Iterator<Item = PathBuf> {
696+
self.get_schema_file_paths().into_iter().map(|schema_path| {
697+
schema_path
698+
.parent()
699+
.expect("A schema in the project root directory is currently not supported.")
700+
.to_owned()
701+
})
702+
}
703+
}
704+
705+
/// Finds the roots of a set of paths. This filters any paths
706+
/// that are a subdirectory of other paths in the input.
707+
fn unify_roots(mut paths: Vec<PathBuf>) -> Vec<PathBuf> {
708+
paths.sort();
709+
let mut roots = Vec::new();
710+
for path in paths {
711+
match roots.last() {
712+
Some(prev) if path.starts_with(prev) => {
713+
// skip
714+
}
715+
_ => {
716+
roots.push(path);
717+
}
718+
}
719+
}
720+
roots
721+
}
722+
723+
#[cfg(test)]
724+
mod test {
725+
use super::*;
726+
727+
#[test]
728+
fn test_unify_roots() {
729+
assert_eq!(unify_roots(vec![]).len(), 0);
730+
assert_eq!(
731+
unify_roots(vec!["Apps".into(), "Libraries".into()]),
732+
&[PathBuf::from("Apps"), PathBuf::from("Libraries")]
733+
);
734+
assert_eq!(
735+
unify_roots(vec!["Apps".into(), "Apps/Foo".into()]),
736+
&[PathBuf::from("Apps")]
737+
);
738+
assert_eq!(
739+
unify_roots(vec!["Apps/Foo".into(), "Apps".into()]),
740+
&[PathBuf::from("Apps")]
741+
);
742+
assert_eq!(
743+
unify_roots(vec!["Foo".into(), "Foo2".into()]),
744+
&[PathBuf::from("Foo"), PathBuf::from("Foo2"),]
745+
);
746+
}
615747
}
616748

617749
impl fmt::Debug for Config {

compiler/crates/relay-compiler/src/file_source/walk_dir_file_source.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,11 @@ impl WalkDirFileSource {
7777
}
7878

7979
fn find_files(&self) -> Vec<File> {
80-
WalkDir::new(self.config.root_dir.clone())
81-
.into_iter()
80+
self.config
81+
.get_all_roots()
82+
.iter()
83+
.map(|source| self.config.root_dir.join(source))
84+
.flat_map(WalkDir::new)
8285
.filter_map(|entry| {
8386
let dir_entry = entry.ok()?;
8487
let relative_path = dir_entry

compiler/crates/relay-compiler/src/file_source/watchman_file_source.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ use relay_saved_state_loader::SavedStateLoader;
2020
pub use watchman_client::prelude::Clock;
2121
use watchman_client::prelude::*;
2222

23-
use super::watchman_query_builder::get_all_roots;
2423
use super::watchman_query_builder::get_watchman_expr;
2524
use super::FileSourceResult;
2625
use crate::compiler_state::CompilerState;
@@ -345,7 +344,8 @@ async fn query_file_result(
345344
..Default::default()
346345
}
347346
} else {
348-
let query_roots = get_all_roots(config)
347+
let query_roots = config
348+
.get_all_roots()
349349
.into_iter()
350350
.map(PathGeneratorElement::RecursivePath)
351351
.collect();

compiler/crates/relay-compiler/src/file_source/watchman_query_builder.rs

Lines changed: 5 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use watchman_client::prelude::*;
1414

1515
use crate::compiler_state::ProjectSet;
1616
use crate::config::Config;
17-
use crate::config::SchemaLocation;
1817

1918
type FnvIndexMap<K, V> = IndexMap<K, V, FnvBuildHasher>;
2019

@@ -45,28 +44,28 @@ pub fn get_watchman_expr(config: &Config) -> Expr {
4544
expressions.push(expr_any(generated_sources_dir_exprs));
4645
}
4746

48-
let output_dir_paths = get_output_dir_paths(config);
47+
let output_dir_paths = config.get_output_dir_paths();
4948
if !output_dir_paths.is_empty() {
5049
let output_dir_expr = expr_files_in_dirs(output_dir_paths);
5150
expressions.push(output_dir_expr);
5251
}
5352

54-
let schema_file_paths = get_schema_file_paths(config);
53+
let schema_file_paths = config.get_schema_file_paths();
5554
if !schema_file_paths.is_empty() {
5655
let schema_file_expr = Expr::Name(NameTerm {
57-
paths: get_schema_file_paths(config),
56+
paths: config.get_schema_file_paths(),
5857
wholename: true,
5958
});
6059
expressions.push(schema_file_expr);
6160
}
6261

63-
let schema_dir_paths = get_schema_dir_paths(config);
62+
let schema_dir_paths = config.get_schema_dir_paths();
6463
if !schema_dir_paths.is_empty() {
6564
let schema_dir_expr = expr_graphql_files_in_dirs(schema_dir_paths);
6665
expressions.push(schema_dir_expr);
6766
}
6867

69-
let extension_roots = get_extension_roots(config);
68+
let extension_roots = config.get_extension_roots();
7069
if !extension_roots.is_empty() {
7170
let extensions_expr = expr_graphql_files_in_dirs(extension_roots);
7271
expressions.push(extensions_expr);
@@ -122,98 +121,6 @@ fn get_project_file_ext_expr(typegen_language: TypegenLanguage) -> Expr {
122121
})
123122
}
124123

125-
/// Compute all root paths that we need to query Watchman with. All files
126-
/// relevant to the compiler should be in these directories.
127-
pub fn get_all_roots(config: &Config) -> Vec<PathBuf> {
128-
let source_roots = get_source_roots(config);
129-
let extra_sources_roots = get_generated_sources_roots(config);
130-
let output_roots = get_output_dir_paths(config);
131-
let extension_roots = get_extension_roots(config);
132-
let schema_file_roots = get_schema_file_roots(config);
133-
let schema_dir_roots = get_schema_dir_paths(config);
134-
unify_roots(
135-
source_roots
136-
.into_iter()
137-
.chain(extra_sources_roots)
138-
.chain(output_roots)
139-
.chain(extension_roots)
140-
.chain(schema_file_roots)
141-
.chain(schema_dir_roots)
142-
.collect(),
143-
)
144-
}
145-
146-
/// Returns all root directories of JS source files for the config.
147-
fn get_source_roots(config: &Config) -> Vec<PathBuf> {
148-
config.sources.keys().cloned().collect()
149-
}
150-
151-
/// Returns all root directories of JS source files for the config.
152-
fn get_generated_sources_roots(config: &Config) -> Vec<PathBuf> {
153-
config.generated_sources.keys().cloned().collect()
154-
}
155-
156-
/// Returns all root directories of GraphQL schema extension files for the
157-
/// config.
158-
fn get_extension_roots(config: &Config) -> Vec<PathBuf> {
159-
config
160-
.projects
161-
.values()
162-
.flat_map(|project_config| project_config.schema_extensions.iter().cloned())
163-
.collect()
164-
}
165-
166-
/// Returns all output and extra artifact output directories for the config.
167-
fn get_output_dir_paths(config: &Config) -> Vec<PathBuf> {
168-
let output_dirs = config
169-
.projects
170-
.values()
171-
.filter_map(|project_config| project_config.output.clone());
172-
173-
let extra_artifact_output_dirs = config
174-
.projects
175-
.values()
176-
.filter_map(|project_config| project_config.extra_artifacts_output.clone());
177-
178-
output_dirs.chain(extra_artifact_output_dirs).collect()
179-
}
180-
181-
/// Returns all paths that contain GraphQL schema files for the config.
182-
fn get_schema_file_paths(config: &Config) -> Vec<PathBuf> {
183-
config
184-
.projects
185-
.values()
186-
.filter_map(|project_config| match &project_config.schema_location {
187-
SchemaLocation::File(schema_file) => Some(schema_file.clone()),
188-
SchemaLocation::Directory(_) => None,
189-
})
190-
.collect()
191-
}
192-
193-
/// Returns all GraphQL schema directories for the config.
194-
fn get_schema_dir_paths(config: &Config) -> Vec<PathBuf> {
195-
config
196-
.projects
197-
.values()
198-
.filter_map(|project_config| match &project_config.schema_location {
199-
SchemaLocation::File(_) => None,
200-
SchemaLocation::Directory(schema_dir) => Some(schema_dir.clone()),
201-
})
202-
.collect()
203-
}
204-
205-
/// Returns root directories that contain GraphQL schema files.
206-
fn get_schema_file_roots(config: &Config) -> impl Iterator<Item = PathBuf> {
207-
get_schema_file_paths(config)
208-
.into_iter()
209-
.map(|schema_path| {
210-
schema_path
211-
.parent()
212-
.expect("A schema in the project root directory is currently not supported.")
213-
.to_owned()
214-
})
215-
}
216-
217124
fn expr_files_in_dirs(roots: Vec<PathBuf>) -> Expr {
218125
expr_any(
219126
roots
@@ -244,47 +151,3 @@ fn expr_any(expressions: Vec<Expr>) -> Expr {
244151
_ => Expr::Any(expressions),
245152
}
246153
}
247-
248-
/// Finds the roots of a set of paths. This filters any paths
249-
/// that are a subdirectory of other paths in the input.
250-
fn unify_roots(mut paths: Vec<PathBuf>) -> Vec<PathBuf> {
251-
paths.sort();
252-
let mut roots = Vec::new();
253-
for path in paths {
254-
match roots.last() {
255-
Some(prev) if path.starts_with(prev) => {
256-
// skip
257-
}
258-
_ => {
259-
roots.push(path);
260-
}
261-
}
262-
}
263-
roots
264-
}
265-
266-
#[cfg(test)]
267-
mod test {
268-
use super::*;
269-
270-
#[test]
271-
fn test_unify_roots() {
272-
assert_eq!(unify_roots(vec![]).len(), 0);
273-
assert_eq!(
274-
unify_roots(vec!["Apps".into(), "Libraries".into()]),
275-
&[PathBuf::from("Apps"), PathBuf::from("Libraries")]
276-
);
277-
assert_eq!(
278-
unify_roots(vec!["Apps".into(), "Apps/Foo".into()]),
279-
&[PathBuf::from("Apps")]
280-
);
281-
assert_eq!(
282-
unify_roots(vec!["Apps/Foo".into(), "Apps".into()]),
283-
&[PathBuf::from("Apps")]
284-
);
285-
assert_eq!(
286-
unify_roots(vec!["Foo".into(), "Foo2".into()]),
287-
&[PathBuf::from("Foo"), PathBuf::from("Foo2"),]
288-
);
289-
}
290-
}

0 commit comments

Comments
 (0)