From be0d7a389fc84fcbe65f11021dc17b0c0787ac09 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sat, 25 Jun 2022 13:35:46 -0700 Subject: [PATCH] new: Only load project `package.json` and `tsconfig.json` once. (#163) * Start. * Move other calls. * Convert tsconfig. * Enable moon workflow. * Enable when rust changes. * Only use once cell. * Add track caller. --- .github/workflows/moon-stub.yml | 19 --- .github/workflows/moon.yml | 4 + crates/project/Cargo.toml | 1 + crates/project/src/project.rs | 109 ++++++++++++++---- crates/project/tests/project_test.rs | 4 +- .../workspace/src/actions/hashing/target.rs | 16 +-- crates/workspace/src/actions/sync_project.rs | 13 ++- 7 files changed, 108 insertions(+), 58 deletions(-) delete mode 100644 .github/workflows/moon-stub.yml diff --git a/.github/workflows/moon-stub.yml b/.github/workflows/moon-stub.yml deleted file mode 100644 index 27246005f14..00000000000 --- a/.github/workflows/moon-stub.yml +++ /dev/null @@ -1,19 +0,0 @@ -# https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks -name: Moon -on: - pull_request: - paths-ignore: - - packages/** - - website/** - - package.json - - yarn.lock -jobs: - ci: - name: CI - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest] - node-version: [14, 16, 18] - steps: - - run: 'echo "Not affected, skipping"' diff --git a/.github/workflows/moon.yml b/.github/workflows/moon.yml index 20d03c5b2ed..59f24e42176 100644 --- a/.github/workflows/moon.yml +++ b/.github/workflows/moon.yml @@ -5,6 +5,10 @@ on: - master pull_request: paths: + - .github/workflows/moon.yml + - .moon/workspace.yml + - .moon/project.yml + - crates/** - packages/** - website/** - package.json diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 3f02c7be289..f5d4700f2c2 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -15,6 +15,7 @@ petgraph = "0.6.0" serde = { version = "1.0.137", features = ["derive"] } serde_json = { version = "1.0.81", features = ["preserve_order"] } thiserror = "1.0.31" +tokio = { version = "1.18.2", features = ["full"] } [dev-dependencies] insta = "1.14.0" diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 67fdd393f60..9f0d0290864 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -12,6 +12,7 @@ use moon_utils::path; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; +use tokio::sync::OnceCell; pub type FileGroupsMap = HashMap; @@ -222,7 +223,7 @@ fn create_tasks_from_config( Ok(tasks) } -#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Project { /// Project configuration loaded from "project.yml", if it exists. @@ -238,6 +239,10 @@ pub struct Project { #[serde(skip)] pub log_target: String, + // The `package.json` in the project root. + #[serde(skip)] + pub package_json: OnceCell, + /// Absolute path to the project's root folder. pub root: PathBuf, @@ -246,6 +251,37 @@ pub struct Project { /// Tasks specific to the project. Inherits all tasks from the global config. pub tasks: TasksMap, + + // The `tsconfig.json` in the project root. + #[serde(skip)] + pub tsconfig_json: OnceCell, +} + +impl Default for Project { + fn default() -> Self { + Project { + config: None, + file_groups: HashMap::new(), + id: String::new(), + log_target: String::new(), + package_json: OnceCell::new(), + root: PathBuf::new(), + source: String::new(), + tasks: HashMap::new(), + tsconfig_json: OnceCell::new(), + } + } +} + +impl PartialEq for Project { + fn eq(&self, other: &Self) -> bool { + self.config == other.config + && self.file_groups == other.file_groups + && self.id == other.id + && self.root == other.root + && self.source == other.source + && self.tasks == other.tasks + } } impl Logable for Project { @@ -293,9 +329,11 @@ impl Project { file_groups, id: String::from(id), log_target, + package_json: OnceCell::new(), root, source: String::from(source), tasks, + tsconfig_json: OnceCell::new(), }) } @@ -314,7 +352,9 @@ impl Project { /// Return the "package.json" name, if the file exists. pub async fn get_package_name(&self) -> Result, ProjectError> { - if let Some(json) = self.load_package_json().await? { + self.load_package_json().await?; + + if let Some(json) = self.package_json.get() { if let Some(name) = &json.name { return Ok(Some(name.clone())); } @@ -335,48 +375,67 @@ impl Project { } /// Load and parse the package's `package.json` if it exists. - pub async fn load_package_json(&self) -> Result, ProjectError> { - let package_path = self.root.join("package.json"); + #[track_caller] + pub async fn load_package_json(&self) -> Result { + if self.package_json.initialized() { + return Ok(true); + } - trace!( - target: self.get_log_target(), - "Attempting to find {} in {}", - color::file("package.json"), - color::path(&self.root), - ); + let package_path = self.root.join("package.json"); if package_path.exists() { + trace!( + target: self.get_log_target(), + "Loading {} in {}", + color::file("package.json"), + color::path(&self.root), + ); + return match PackageJson::load(&package_path).await { - Ok(cfg) => Ok(Some(cfg)), + Ok(json) => { + self.package_json + .set(json) + .expect("Failed to load package.json"); + + Ok(true) + } Err(error) => Err(ProjectError::Moon(error)), }; } - Ok(None) + Ok(false) } /// Load and parse the package's `tsconfig.json` if it exists. - pub async fn load_tsconfig_json( - &self, - tsconfig_name: &str, - ) -> Result, ProjectError> { - let tsconfig_path = self.root.join(tsconfig_name); + #[track_caller] + pub async fn load_tsconfig_json(&self, tsconfig_name: &str) -> Result { + if self.tsconfig_json.initialized() { + return Ok(true); + } - trace!( - target: self.get_log_target(), - "Attempting to find {} in {}", - color::file(tsconfig_name), - color::path(&self.root), - ); + let tsconfig_path = self.root.join(tsconfig_name); if tsconfig_path.exists() { + trace!( + target: self.get_log_target(), + "Loading {} in {}", + color::file(tsconfig_name), + color::path(&self.root), + ); + return match TsConfigJson::load(&tsconfig_path).await { - Ok(cfg) => Ok(Some(cfg)), + Ok(json) => { + self.tsconfig_json + .set(json) + .expect("Failed to load tsconfig.json"); + + Ok(true) + } Err(error) => Err(ProjectError::Moon(error)), }; } - Ok(None) + Ok(false) } /// Return the project as a JSON string. diff --git a/crates/project/tests/project_test.rs b/crates/project/tests/project_test.rs index ef3689ca671..287133c4d19 100644 --- a/crates/project/tests/project_test.rs +++ b/crates/project/tests/project_test.rs @@ -206,8 +206,10 @@ async fn has_package_json() { ) .unwrap(); + project.load_package_json().await.unwrap(); + assert_eq!( - project.load_package_json().await.unwrap().unwrap(), + *project.package_json.get().unwrap(), PackageJson { path: workspace_root.join("projects/package-json/package.json"), name: Some(String::from("npm-example")), diff --git a/crates/workspace/src/actions/hashing/target.rs b/crates/workspace/src/actions/hashing/target.rs index d3e2038b2cd..b8c7ea3def1 100644 --- a/crates/workspace/src/actions/hashing/target.rs +++ b/crates/workspace/src/actions/hashing/target.rs @@ -50,15 +50,17 @@ pub async fn create_target_hasher( } // Hash project configs second so they can override - if let Some(package) = project.load_package_json().await? { - hasher.hash_package_json(&package); + project.load_package_json().await?; + project + .load_tsconfig_json(&workspace.config.typescript.project_config_file_name) + .await?; + + if let Some(package) = project.package_json.get() { + hasher.hash_package_json(package); } - if let Some(tsconfig) = project - .load_tsconfig_json(&workspace.config.typescript.project_config_file_name) - .await? - { - hasher.hash_tsconfig_json(&tsconfig); + if let Some(tsconfig) = project.tsconfig_json.get() { + hasher.hash_tsconfig_json(tsconfig); } // For input files, hash them with the vcs layer first diff --git a/crates/workspace/src/actions/sync_project.rs b/crates/workspace/src/actions/sync_project.rs index f9c6025e09d..31c3e50535e 100644 --- a/crates/workspace/src/actions/sync_project.rs +++ b/crates/workspace/src/actions/sync_project.rs @@ -46,25 +46,26 @@ pub async fn sync_project( // Read only { let workspace = workspace.read().await; - let project = workspace.projects.load(project_id)?; + let mut project = workspace.projects.load(project_id)?; let node_config = &workspace.config.node; // Copy values outside of this block typescript_config = workspace.config.typescript.clone(); // Sync each dependency to `tsconfig.json` and `package.json` - let package_manager = workspace.toolchain.get_node().get_package_manager(); - let mut project_package_json = project.load_package_json().await?; - let mut project_tsconfig_json = project + project.load_package_json().await?; + project .load_tsconfig_json(&typescript_config.project_config_file_name) .await?; + let package_manager = workspace.toolchain.get_node().get_package_manager(); + for dep_id in project.get_dependencies() { let dep_project = workspace.projects.load(&dep_id)?; // Update `dependencies` within this project's `package.json` if node_config.sync_project_workspace_dependencies { - if let Some(package_json) = &mut project_package_json { + if let Some(package_json) = project.package_json.get_mut() { let dep_package_name = dep_project.get_package_name().await?.unwrap_or_default(); @@ -93,7 +94,7 @@ pub async fn sync_project( // Update `references` within this project's `tsconfig.json` if typescript_config.sync_project_references { - if let Some(tsconfig_json) = &mut project_tsconfig_json { + if let Some(tsconfig_json) = project.tsconfig_json.get_mut() { let tsconfig_branch_name = &typescript_config.project_config_file_name; let dep_ref_path = String::from( diff_paths(&dep_project.root, &project.root)