From 192bfe72d9f453b2e137bebcad11989233ac855d Mon Sep 17 00:00:00 2001 From: Jason Jean Date: Mon, 9 Dec 2024 11:01:07 -0500 Subject: [PATCH] fix(core): hashing fixes (#29247) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Current Behavior Hashing files via the workspace context sometimes resulted in a falsely identical hashes when files are renamed.. but remain in the same order alphabetically. This should be somewhat rare and most likely other changes would accompany the rename which would change the overall hash. This hashing was not used in task hashing though so it would not affect that. Debugging hashing inconsistencies is currently difficult as there is an overload of logging:
```sh ~/p/nx-test (master↓24|✔) [1]$ NX_NATIVE_LOGGING='nx::native::tasks::hashers::hash_workspace_files=trace' NX_DAEMON=false nx lint nx TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/nx.json TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/.gitignore TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/.nxignore TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/nx.json TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/.gitignore TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/.nxignore TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/babel.config.json TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/.nx/workflows/agents.yaml TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/.circleci/config.yml TRACE nx::native::tasks::hashers::hash_workspace_files: ".circleci/config.yml" was found with glob ["babel.config.json", ".nx/workflows/agents.yaml", ".circleci/config.yml"] TRACE nx::native::tasks::hashers::hash_workspace_files: "babel.config.json" was found with glob ["babel.config.json", ".nx/workflows/agents.yaml", ".circleci/config.yml"] TRACE nx::native::tasks::hashers::hash_workspace_files: ".gitignore" was found with glob ["nx.json", ".gitignore", ".nxignore"] TRACE nx::native::tasks::hashers::hash_workspace_files: ".nxignore" was found with glob ["nx.json", ".gitignore", ".nxignore"] TRACE nx::native::tasks::hashers::hash_workspace_files: ".gitignore" was found with glob ["nx.json", ".gitignore", ".nxignore"] TRACE nx::native::tasks::hashers::hash_workspace_files: ".nxignore" was found with glob ["nx.json", ".gitignore", ".nxignore"] TRACE nx::native::tasks::hashers::hash_workspace_files: "nx.json" was found with glob ["nx.json", ".gitignore", ".nxignore"] TRACE nx::native::tasks::hashers::hash_workspace_files: "nx.json" was found with glob ["nx.json", ".gitignore", ".nxignore"] TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/babel.config.json TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/.nx/workflows/agents.yaml TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/.circleci/config.yml TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/.eslintrc.json TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/tools/eslint-rules/**/* TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/babel.config.json TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/.nx/workflows/agents.yaml TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/.circleci/config.yml TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/nx.json TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/.gitignore TRACE nx::native::tasks::hashers::hash_workspace_files: Workspace file set: {workspaceRoot}/.nxignore TRACE nx::native::tasks::hashers::hash_workspace_files: ".circleci/config.yml" was found with glob ["babel.config.json", ".nx/workflows/agents.yaml", ".circleci/config.yml", ".eslintrc.json", "tools/eslint-rules/**/*"] TRACE nx::native::tasks::hashers::hash_workspace_files: ".eslintrc.json" was found with glob ["babel.config.json", ".nx/workflows/agents.yaml", ".circleci/config.yml", ".eslintrc.json", "tools/eslint-rules/**/*"] TRACE nx::native::tasks::hashers::hash_workspace_files: "babel.config.json" was found with glob ["babel.config.json", ".nx/workflows/agents.yaml", ".circleci/config.yml", ".eslintrc.json", "tools/eslint-rules/**/*"] TRACE nx::native::tasks::hashers::hash_workspace_files: "tools/eslint-rules/index.ts" was found with glob ["babel.config.json", ".nx/workflows/agents.yaml", ".circleci/config.yml", ".eslintrc.json", "tools/eslint-rules/**/*"] TRACE nx::native::tasks::hashers::hash_workspace_files: "tools/eslint-rules/jest.config.ts" was found with glob ["babel.config.json", ".nx/workflows/agents.yaml", ".circleci/config.yml", ".eslintrc.json", "tools/eslint-rules/**/*"] TRACE nx::native::tasks::hashers::hash_workspace_files: "tools/eslint-rules/project.json" was found with glob ["babel.config.json", ".nx/workflows/agents.yaml", ".circleci/config.yml", ".eslintrc.json", "tools/eslint-rules/**/*"] TRACE nx::native::tasks::hashers::hash_workspace_files: "tools/eslint-rules/raw-file-parser.js" was found with glob ["babel.config.json", ".nx/workflows/agents.yaml", ".circleci/config.yml", ".eslintrc.json", "tools/eslint-rules/**/*"] TRACE nx::native::tasks::hashers::hash_workspace_files: "tools/eslint-rules/rules/ensure-pnpm-lock-version.ts" was found with glob ["babel.config.json", ".nx/workflows/agents.yaml", ".circleci/config.yml", ".eslintrc.json", "tools/eslint-rules/**/*"] TRACE nx::native::tasks::hashers::hash_workspace_files: "tools/eslint-rules/rules/valid-command-object.spec.ts" was found with glob ["babel.config.json", ".nx/workflows/agents.yaml", ".circleci/config.yml", ".eslintrc.json", "tools/eslint-rules/**/*"] TRACE nx::native::tasks::hashers::hash_workspace_files: "tools/eslint-rules/rules/valid-command-object.ts" was found with glob ["babel.config.json", ".nx/workflows/agents.yaml", ".circleci/config.yml", ".eslintrc.json", "tools/eslint-rules/**/*"] TRACE nx::native::tasks::hashers::hash_workspace_files: "tools/eslint-rules/rules/valid-schema-description.spec.ts" was found with glob ["babel.config.json", ".nx/workflows/agents.yaml", ".circleci/config.yml", ".eslintrc.json", "tools/eslint-rules/**/*"] TRACE nx::native::tasks::hashers::hash_workspace_files: "tools/eslint-rules/rules/valid-schema-description.ts" was found with glob ["babel.config.json", ".nx/workflows/agents.yaml", ".circleci/config.yml", ".eslintrc.json", "tools/eslint-rules/**/*"] TRACE nx::native::tasks::hashers::hash_workspace_files: "tools/eslint-rules/tsconfig.json" was found with glob ["babel.config.json", ".nx/workflows/agents.yaml", ".circleci/config.yml", ".eslintrc.json", "tools/eslint-rules/**/*"] TRACE nx::native::tasks::hashers::hash_workspace_files: "tools/eslint-rules/tsconfig.lint.json" was found with glob ["babel.config.json", ".nx/workflows/agents.yaml", ".circleci/config.yml", ".eslintrc.json", "tools/eslint-rules/**/*"] TRACE nx::native::tasks::hashers::hash_workspace_files: "tools/eslint-rules/tsconfig.spec.json" was found with glob ["babel.config.json", ".nx/workflows/agents.yaml", ".circleci/config.yml", ".eslintrc.json", "tools/eslint-rules/**/*"] ```
## Expected Behavior Hashing files via the workspace context no longer produces falsely identical hashes when files are named. Debugging hashing inconsistencies is better now. It produces the hash and the file it is adding to the workspace file hashset in the order which it is added. Different filesets are still handled in parallel with one another so the logging is best filtered through `grep` for a particular fileset:
```sh ~/p/nx (hash-logging|✔) $ NX_NATIVE_LOGGING='nx::native::tasks::hashers::hash_workspace_files=debug' NX_DAEMON=false nx lint nx | grep "nx.json" DEBUG nx::native::tasks::hashers::hash_workspace_files: Hashing workspace fileset{cache_key="nx.json,.gitignore,.nxignore"}: Adding "5069375034190792089" (".gitignore") to hash DEBUG nx::native::tasks::hashers::hash_workspace_files: Hashing workspace fileset{cache_key="nx.json,.gitignore,.nxignore"}: Adding "10752854809791558346" (".nxignore") to hash DEBUG nx::native::tasks::hashers::hash_workspace_files: Hashing workspace fileset{cache_key="nx.json,.gitignore,.nxignore"}: Adding "9876981562233255395" ("nx.json") to hash DEBUG nx::native::tasks::hashers::hash_workspace_files: Hashing workspace fileset{cache_key="nx.json,.gitignore,.nxignore"}: Hash Value: "12458994942476116599" ```
## Related Issue(s) Fixes # --- .../hasher/native-task-hasher-impl.spec.ts | 41 ++++++----- packages/nx/src/native/glob.rs | 1 + .../tasks/hashers/hash_workspace_files.rs | 69 ++++++++++++++----- packages/nx/src/native/tasks/task_hasher.rs | 20 +++--- .../src/native/tests/workspace_files.spec.ts | 45 +++++++++++- packages/nx/src/native/workspace/context.rs | 24 ++++--- 6 files changed, 141 insertions(+), 59 deletions(-) diff --git a/packages/nx/src/hasher/native-task-hasher-impl.spec.ts b/packages/nx/src/hasher/native-task-hasher-impl.spec.ts index f606427054dbc..9de88758bcc9f 100644 --- a/packages/nx/src/hasher/native-task-hasher-impl.spec.ts +++ b/packages/nx/src/hasher/native-task-hasher-impl.spec.ts @@ -4,7 +4,6 @@ import { NxJsonConfiguration } from '../config/nx-json'; import { createTaskGraph } from '../tasks-runner/create-task-graph'; import { NativeTaskHasherImpl } from './native-task-hasher-impl'; import { ProjectGraphBuilder } from '../project-graph/project-graph-builder'; -import { testOnlyTransferFileMap } from '../native'; describe('native task hasher', () => { let tempFs: TempFs; @@ -166,9 +165,9 @@ describe('native task hasher', () => { "unrelated:ProjectConfiguration": "11133337791644294114", "unrelated:TsConfig": "2264969541778889434", "unrelated:{projectRoot}/**/*": "4127219831408253695", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "18099427347122160586", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "6993407921919898285", }, - "value": "391066910278240047", + "value": "15987635381237972716", }, ] `); @@ -232,9 +231,9 @@ describe('native task hasher', () => { "parent:ProjectConfiguration": "8031122597231773116", "parent:TsConfig": "2264969541778889434", "parent:{projectRoot}/**/*": "17059468255294227635", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "18099427347122160586", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "6993407921919898285", }, - "value": "2068118780828544905", + "value": "10262178246623018030", } `); }); @@ -312,9 +311,9 @@ describe('native task hasher', () => { "parent:!{projectRoot}/**/*.spec.ts": "8911122541468969799", "parent:ProjectConfiguration": "3608670998275221195", "parent:TsConfig": "2264969541778889434", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "11114659294156087056", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "9567402949680805009", }, - "value": "7780216706447676384", + "value": "14320402761058545796", } `); }); @@ -379,9 +378,9 @@ describe('native task hasher', () => { "parent:!{projectRoot}/**/*.spec.ts": "8911122541468969799", "parent:ProjectConfiguration": "16402137858974842465", "parent:TsConfig": "2264969541778889434", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "11114659294156087056", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "9567402949680805009", }, - "value": "16063851723942996830", + "value": "2453961902871518313", }, { "details": { @@ -390,9 +389,9 @@ describe('native task hasher', () => { "parent:ProjectConfiguration": "16402137858974842465", "parent:TsConfig": "2264969541778889434", "parent:{projectRoot}/**/*": "17059468255294227635", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "11114659294156087056", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "9567402949680805009", }, - "value": "1153029350223570014", + "value": "5894031627295207190", }, ] `); @@ -480,11 +479,11 @@ describe('native task hasher', () => { "parent:ProjectConfiguration": "14398811678394411425", "parent:TsConfig": "2264969541778889434", "parent:{projectRoot}/**/*": "17059468255294227635", - "workspace:[{workspaceRoot}/global1]": "14542405497386871555", - "workspace:[{workspaceRoot}/global2]": "12932836274958677781", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "12076281115618125366", + "workspace:[{workspaceRoot}/global1]": "11580065831422255455", + "workspace:[{workspaceRoot}/global2]": "6389465682922235219", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "1359893257201181462", }, - "value": "11623032905580707496", + "value": "12394084267697729491", }, ] `); @@ -537,9 +536,9 @@ describe('native task hasher', () => { "parent:ProjectConfiguration": "3608670998275221195", "parent:TsConfig": "8661678577354855152", "parent:{projectRoot}/**/*": "17059468255294227635", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "18099427347122160586", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "6993407921919898285", }, - "value": "15449891577656158381", + "value": "16657264716563422624", } `); }); @@ -616,9 +615,9 @@ describe('native task hasher', () => { "parent:ProjectConfiguration": "3608670998275221195", "parent:TsConfig": "2264969541778889434", "parent:{projectRoot}/**/*": "17059468255294227635", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "18099427347122160586", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "6993407921919898285", }, - "value": "7701541978018526456", + "value": "1325637283470296766", } `); @@ -639,9 +638,9 @@ describe('native task hasher', () => { "parent:ProjectConfiguration": "3608670998275221195", "parent:TsConfig": "2264969541778889434", "parent:{projectRoot}/**/*": "17059468255294227635", - "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "18099427347122160586", + "workspace:[{workspaceRoot}/nx.json,{workspaceRoot}/.gitignore,{workspaceRoot}/.nxignore]": "6993407921919898285", }, - "value": "7701541978018526456", + "value": "1325637283470296766", } `); }); diff --git a/packages/nx/src/native/glob.rs b/packages/nx/src/native/glob.rs index d91ab482d294c..6f4e995815c53 100644 --- a/packages/nx/src/native/glob.rs +++ b/packages/nx/src/native/glob.rs @@ -59,6 +59,7 @@ impl NxGlobSetBuilder { } } +#[derive(Debug)] pub struct NxGlobSet { included_globs: GlobSet, excluded_globs: GlobSet, diff --git a/packages/nx/src/native/tasks/hashers/hash_workspace_files.rs b/packages/nx/src/native/tasks/hashers/hash_workspace_files.rs index 6848b7c8cf4d2..53d07ce720399 100644 --- a/packages/nx/src/native/tasks/hashers/hash_workspace_files.rs +++ b/packages/nx/src/native/tasks/hashers/hash_workspace_files.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use anyhow::*; use dashmap::DashMap; -use tracing::{trace, warn}; +use tracing::{debug, debug_span, trace, warn}; use crate::native::types::FileData; use crate::native::{glob::build_glob_set, hasher::hash}; @@ -47,20 +47,21 @@ pub fn hash_workspace_files( let glob = build_glob_set(&globs)?; let mut hasher = xxhash_rust::xxh3::Xxh3::new(); - let mut hashes: Vec = Vec::new(); - for file in all_workspace_files - .iter() - .filter(|file| glob.is_match(&file.file)) - { - trace!("{:?} was found with glob {:?}", file.file, globs); - hashes.push(file.hash.clone()); - hashes.push(file.file.clone()); - } - hasher.update(hashes.join(",").as_bytes()); - let hashed_value = hasher.digest().to_string(); + debug_span!("Hashing workspace fileset", cache_key).in_scope(|| { + for file in all_workspace_files + .iter() + .filter(|file| glob.is_match(&file.file)) + { + debug!("Adding {:?} ({:?}) to hash", file.hash, file.file); + hasher.update(file.file.clone().as_bytes()); + hasher.update(file.hash.clone().as_bytes()); + } + let hashed_value = hasher.digest().to_string(); + debug!("Hash Value: {:?}", hashed_value); - cache.insert(cache_key.to_string(), hashed_value.clone()); - Ok(hashed_value) + cache.insert(cache_key.to_string(), hashed_value.clone()); + Ok(hashed_value) + }) } #[cfg(test)] @@ -111,9 +112,41 @@ mod test { Arc::new(DashMap::new()), ) .unwrap(); - assert_eq!(result, hash([ - gitignore_file.hash, - gitignore_file.file - ].join(",").as_bytes())); + assert_eq!(result, "15841935230129999746"); + } + + #[test] + fn test_hash_workspace_files_is_deterministic() { + let gitignore_file = FileData { + file: ".gitignore".into(), + hash: "123".into(), + }; + let nxignore_file = FileData { + file: ".nxignore".into(), + hash: "456".into(), + }; + let package_json_file = FileData { + file: "package.json".into(), + hash: "789".into(), + }; + let project_file = FileData { + file: "packages/project/project.json".into(), + hash: "abc".into(), + }; + + for i in 0..1000 { + let result = hash_workspace_files( + &["{workspaceRoot}/**/*".to_string()], + &[ + gitignore_file.clone(), + nxignore_file.clone(), + package_json_file.clone(), + project_file.clone(), + ], + Arc::new(DashMap::new()), + ) + .unwrap(); + assert_eq!(result, "13759877301064854697"); + } } } diff --git a/packages/nx/src/native/tasks/task_hasher.rs b/packages/nx/src/native/tasks/task_hasher.rs index 5e8055a6d4635..4c52843eaa4f0 100644 --- a/packages/nx/src/native/tasks/task_hasher.rs +++ b/packages/nx/src/native/tasks/task_hasher.rs @@ -82,8 +82,8 @@ impl TaskHasher { hash_plans: External>>, js_env: HashMap, ) -> anyhow::Result> { - debug!("hashing plans {:?}", hash_plans.as_ref()); - trace!("plan length: {}", hash_plans.len()); + trace!("hashing plans {:?}", hash_plans.as_ref()); + debug!("plan length: {}", hash_plans.len()); trace!("all workspace files: {}", self.all_workspace_files.len()); trace!("project_file_map: {}", self.project_file_map.len()); @@ -136,18 +136,22 @@ impl TaskHasher { })?; hashes.iter_mut().for_each(|mut h| { - let hash_details = h.value_mut(); + let (hash_id, hash_details) = h.pair_mut(); let mut keys = hash_details.details.keys().collect::>(); keys.par_sort(); let mut hasher = xxhash_rust::xxh3::Xxh3::new(); - for key in keys { - hasher.update(hash_details.details[key].as_bytes()); - } - hash_details.value = hasher.digest().to_string(); + trace_span!("Assembling hash", hash_id).in_scope(|| { + for key in keys { + trace!("Adding {} ({}) to hash", hash_details.details[key], key); + hasher.update(hash_details.details[key].as_bytes()); + } + let hash = hasher.digest().to_string(); + trace!("Hash Value: {}", hash); + hash_details.value = hash; + }); }); trace!("hashing took {:?}", hash_time.elapsed()); - debug!(?hashes); Ok(hashes) } diff --git a/packages/nx/src/native/tests/workspace_files.spec.ts b/packages/nx/src/native/tests/workspace_files.spec.ts index 537b6af6eeb09..736cf0e3c278e 100644 --- a/packages/nx/src/native/tests/workspace_files.spec.ts +++ b/packages/nx/src/native/tests/workspace_files.spec.ts @@ -5,7 +5,7 @@ import { dirname, join } from 'path'; import { readJsonFile } from '../../utils/fileutils'; import { cacheDirectoryForWorkspace } from '../../utils/cache-directory'; -describe('workspace files', () => { +describe('Workspace Context', () => { function createParseConfigurationsFunction(tempDir: string) { return async (filenames: string[]) => { const res = {}; @@ -187,6 +187,49 @@ describe('workspace files', () => { `); }); + describe('hashing', () => { + let context: WorkspaceContext; + let fs: TempFs; + + beforeEach(async () => { + fs = new TempFs('workspace-files'); + + const files = {}; + for (let i = 0; i < 1000; i++) { + files[`file${i}.txt`] = i.toString(); + } + + await fs.createFiles(files); + + context = new WorkspaceContext( + fs.tempDir, + cacheDirectoryForWorkspace(fs.tempDir) + ); + }); + + it('should hash consistently when nothing changes', () => { + let hash = context.hashFilesMatchingGlob(['**/*.txt']); + for (let i = 0; i < 100; i++) { + const newContext = new WorkspaceContext( + fs.tempDir, + cacheDirectoryForWorkspace(fs.tempDir) + ); + expect(newContext.hashFilesMatchingGlob(['**/*.txt'])).toEqual(hash); + } + }); + + it('should hash differently if a file is renamed', () => { + let hash1 = context.hashFilesMatchingGlob(['**/*.txt']); + const newContext = new WorkspaceContext( + fs.tempDir, + cacheDirectoryForWorkspace(fs.tempDir) + ); + fs.renameFile('file0.txt', 'file00.txt'); + let hash2 = newContext.hashFilesMatchingGlob(['**/*.txt']); + expect(hash1).not.toEqual(hash2); + }); + }); + describe('globbing', () => { let context: WorkspaceContext; let fs: TempFs; diff --git a/packages/nx/src/native/workspace/context.rs b/packages/nx/src/native/workspace/context.rs index 75d35000b9d9f..64ef2b595fdda 100644 --- a/packages/nx/src/native/workspace/context.rs +++ b/packages/nx/src/native/workspace/context.rs @@ -3,10 +3,6 @@ use std::ops::Deref; use std::path::{Path, PathBuf}; use std::sync::Arc; -use napi::bindgen_prelude::External; -use rayon::prelude::*; -use tracing::{trace, warn}; - use crate::native::hasher::hash; use crate::native::logger::enable_logger; use crate::native::project_graph::utils::{find_project_for_path, ProjectRootMappings}; @@ -18,6 +14,10 @@ use crate::native::workspace::types::{ FileMap, NxWorkspaceFilesExternals, ProjectFiles, UpdatedWorkspaceFiles, }; use crate::native::workspace::{config_files, types::NxWorkspaceFiles, workspace_files}; +use napi::bindgen_prelude::External; +use rayon::prelude::*; +use tracing::{trace, warn}; +use xxhash_rust::xxh3; #[napi] pub struct WorkspaceContext { @@ -238,13 +238,15 @@ impl WorkspaceContext { exclude: Option>, ) -> napi::Result { let files = &self.all_file_data(); - let globbed_files = config_files::glob_files(files, globs, exclude)?; - Ok(hash( - &globbed_files - .map(|file| file.hash.as_bytes()) - .collect::>() - .concat(), - )) + let globbed_files = config_files::glob_files(files, globs, exclude)?.collect::>(); + + let mut hasher = xxh3::Xxh3::new(); + for file in globbed_files { + hasher.update(file.file.as_bytes()); + hasher.update(file.hash.as_bytes()); + } + + Ok(hasher.digest().to_string()) } #[napi]