diff --git a/crates/turborepo-lib/src/engine/builder.rs b/crates/turborepo-lib/src/engine/builder.rs index 07e3fb673187f..cbf51e09666b8 100644 --- a/crates/turborepo-lib/src/engine/builder.rs +++ b/crates/turborepo-lib/src/engine/builder.rs @@ -436,7 +436,7 @@ mod test { unreachable!() } - fn global_change_key(&self) -> Vec { + fn global_change(&self, _other: &dyn Lockfile) -> bool { unreachable!() } } diff --git a/crates/turborepo-lib/src/package_graph/mod.rs b/crates/turborepo-lib/src/package_graph/mod.rs index bca4803b8dcfc..b32a30ced0783 100644 --- a/crates/turborepo-lib/src/package_graph/mod.rs +++ b/crates/turborepo-lib/src/package_graph/mod.rs @@ -326,10 +326,7 @@ impl PackageGraph { let closures = turborepo_lockfiles::all_transitive_closures(previous, external_deps)?; - let current_key = current.global_change_key(); - let previous_key = previous.global_change_key(); - - let global_change = current_key != previous_key; + let global_change = current.global_change(previous); let changed = if global_change { None @@ -548,8 +545,8 @@ mod test { unreachable!("lockfile encoding not necessary for package graph construction") } - fn global_change_key(&self) -> Vec { - unreachable!() + fn global_change(&self, _other: &dyn Lockfile) -> bool { + unreachable!("global change detection not necessary for package graph construction") } } diff --git a/crates/turborepo-lockfiles/src/berry/mod.rs b/crates/turborepo-lockfiles/src/berry/mod.rs index 514655265f3d3..c1d28377230ad 100644 --- a/crates/turborepo-lockfiles/src/berry/mod.rs +++ b/crates/turborepo-lockfiles/src/berry/mod.rs @@ -5,6 +5,7 @@ mod resolution; mod ser; use std::{ + any::Any, collections::{HashMap, HashSet}, iter, }; @@ -13,7 +14,6 @@ use de::SemverString; use identifiers::{Descriptor, Locator}; use protocol_resolver::DescriptorResolver; use serde::{Deserialize, Serialize}; -use serde_json::json; use thiserror::Error; use turbopath::RelativeUnixPathBuf; @@ -548,19 +548,14 @@ impl Lockfile for BerryLockfile { Ok(patches) } - fn global_change_key(&self) -> Vec { - let mut buf = vec![b'b', b'e', b'r', b'r', b'y', 0]; - - serde_json::to_writer( - &mut buf, - &json!({ - "version": &self.data.metadata.version, - "cache_key": &self.data.metadata.cache_key, - }), - ) - .expect("writing to Vec cannot fail"); - - buf + fn global_change(&self, other: &dyn Lockfile) -> bool { + let any_other = other as &dyn Any; + if let Some(other) = any_other.downcast_ref::() { + self.data.metadata.version != other.data.metadata.version + || self.data.metadata.cache_key != other.data.metadata.cache_key + } else { + true + } } } diff --git a/crates/turborepo-lockfiles/src/bun/mod.rs b/crates/turborepo-lockfiles/src/bun/mod.rs index e7624b34f2f0c..5f46ada7cde32 100644 --- a/crates/turborepo-lockfiles/src/bun/mod.rs +++ b/crates/turborepo-lockfiles/src/bun/mod.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{any::Any, str::FromStr}; use serde::Deserialize; @@ -109,8 +109,11 @@ impl Lockfile for BunLockfile { Err(crate::Error::Bun(Error::NotImplemented())) } - fn global_change_key(&self) -> Vec { - vec![b'b', b'u', b'n', 0] + fn global_change(&self, other: &dyn Lockfile) -> bool { + let any_other = other as &dyn Any; + // Downcast returns none if the concrete type doesn't match + // if the types don't match then we changed package managers + any_other.downcast_ref::().is_none() } } diff --git a/crates/turborepo-lockfiles/src/lib.rs b/crates/turborepo-lockfiles/src/lib.rs index 571ab475b201f..cff0574bc8c58 100644 --- a/crates/turborepo-lockfiles/src/lib.rs +++ b/crates/turborepo-lockfiles/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(trait_upcasting)] #![deny(clippy::all)] mod berry; @@ -7,7 +8,10 @@ mod npm; mod pnpm; mod yarn1; -use std::collections::{HashMap, HashSet}; +use std::{ + any::Any, + collections::{HashMap, HashSet}, +}; pub use berry::{Error as BerryError, *}; pub use bun::BunLockfile; @@ -27,7 +31,7 @@ pub struct Package { // This trait will only be used when migrating the Go lockfile implementations // to Rust. Once the migration is complete we will leverage petgraph for doing // our graph calculations. -pub trait Lockfile: Send + Sync { +pub trait Lockfile: Send + Sync + Any { // Given a workspace, a package it imports and version returns the key, resolved // version, and if it was found fn resolve_package( @@ -53,13 +57,8 @@ pub trait Lockfile: Send + Sync { Ok(Vec::new()) } - /// Present a global change key which is compared against two lockfiles - /// - /// Impl notes: please prefix this key with some magic identifier - /// to prevent clashes. we are not worried about inter-version - /// compatibility so these keys don't need to be stable. They are - /// ephemeral. - fn global_change_key(&self) -> Vec; + /// Determine if there's a global change between two lockfiles + fn global_change(&self, other: &dyn Lockfile) -> bool; } /// Takes a lockfile, and a map of workspace directory paths -> (package name, diff --git a/crates/turborepo-lockfiles/src/npm.rs b/crates/turborepo-lockfiles/src/npm.rs index e3cffea4ba5d3..2cf3aebba395a 100644 --- a/crates/turborepo-lockfiles/src/npm.rs +++ b/crates/turborepo-lockfiles/src/npm.rs @@ -1,7 +1,7 @@ -use std::collections::HashMap; +use std::{any::Any, collections::HashMap}; use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; +use serde_json::Value; use super::{Error, Lockfile, Package}; @@ -136,19 +136,14 @@ impl Lockfile for NpmLockfile { Ok(serde_json::to_vec_pretty(&self)?) } - fn global_change_key(&self) -> Vec { - let mut buf = vec![b'n', b'p', b'm', 0]; - - serde_json::to_writer( - &mut buf, - &json!({ - "requires": &self.other.get("requires"), - "version": &self.lockfile_version, - }), - ) - .expect("writing to Vec cannot fail"); - - buf + fn global_change(&self, other: &dyn Lockfile) -> bool { + let any_other = other as &dyn Any; + if let Some(other) = any_other.downcast_ref::() { + self.lockfile_version != other.lockfile_version + || self.other.get("requires") != other.other.get("requires") + } else { + true + } } } diff --git a/crates/turborepo-lockfiles/src/pnpm/data.rs b/crates/turborepo-lockfiles/src/pnpm/data.rs index 0d9749ae49c67..3a8030f8225e2 100644 --- a/crates/turborepo-lockfiles/src/pnpm/data.rs +++ b/crates/turborepo-lockfiles/src/pnpm/data.rs @@ -1,7 +1,6 @@ -use std::borrow::Cow; +use std::{any::Any, borrow::Cow, collections::BTreeMap}; use serde::{Deserialize, Serialize}; -use serde_json::json; use turbopath::RelativeUnixPathBuf; use super::{dep_path::DepPath, Error, LockfileVersion}; @@ -252,6 +251,27 @@ impl PnpmLockfile { } Ok(pruned_patches) } + + // Create a projection of all fields in the lockfile that could affect all + // workspaces + fn global_fields(&self) -> GlobalFields { + GlobalFields { + version: &self.lockfile_version.version, + checksum: self.package_extensions_checksum.as_deref(), + overrides: self.overrides.as_ref(), + patched_dependencies: self.patched_dependencies.as_ref(), + settings: self.settings.as_ref(), + } + } +} + +#[derive(Debug, PartialEq, Eq)] +struct GlobalFields<'a> { + version: &'a str, + checksum: Option<&'a str>, + overrides: Option<&'a BTreeMap>, + patched_dependencies: Option<&'a BTreeMap>, + settings: Option<&'a LockfileSettings>, } impl crate::Lockfile for PnpmLockfile { @@ -401,22 +421,13 @@ impl crate::Lockfile for PnpmLockfile { Ok(patches) } - fn global_change_key(&self) -> Vec { - let mut buf = vec![b'p', b'n', b'p', b'm', 0]; - - serde_json::to_writer( - &mut buf, - &json!({ - "version": self.lockfile_version.version, - "checksum": self.package_extensions_checksum, - "overrides": self.overrides, - "patched_deps": self.patched_dependencies, - "settings": self.settings, - }), - ) - .expect("writing to Vec cannot fail"); - - buf + fn global_change(&self, other: &dyn crate::Lockfile) -> bool { + let any_other = other as &dyn Any; + if let Some(other) = any_other.downcast_ref::() { + self.global_fields() != other.global_fields() + } else { + true + } } } diff --git a/crates/turborepo-lockfiles/src/yarn1/mod.rs b/crates/turborepo-lockfiles/src/yarn1/mod.rs index e26c65d99f9c6..5ba6b8dcacc36 100644 --- a/crates/turborepo-lockfiles/src/yarn1/mod.rs +++ b/crates/turborepo-lockfiles/src/yarn1/mod.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{any::Any, str::FromStr}; use serde::Deserialize; @@ -108,8 +108,11 @@ impl Lockfile for Yarn1Lockfile { Ok(self.to_string().into_bytes()) } - fn global_change_key(&self) -> Vec { - vec![b'y', b'a', b'r', b'n', 0] + fn global_change(&self, other: &dyn Lockfile) -> bool { + let any_other = other as &dyn Any; + // Downcast returns none if the concrete type doesn't match + // if the types don't match then we changed package managers + any_other.downcast_ref::().is_none() } }