这是indexloc提供的服务,不要输入任何密码
Skip to content

fix: affected_packages's optimization flow #9950

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions crates/turborepo-lib/src/package_changes_watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ use turborepo_filewatch::{
NotifyError, OptionalWatch,
};
use turborepo_repository::{
change_mapper::{ChangeMapper, GlobalDepsPackageChangeMapper, PackageChanges},
change_mapper::{
ChangeMapper, GlobalDepsPackageChangeMapper, LockfileContents, PackageChanges,
},
package_graph::{PackageGraph, PackageGraphBuilder, PackageName, WorkspacePackage},
package_json::PackageJson,
};
Expand Down Expand Up @@ -342,7 +344,8 @@ impl Subscriber {
continue;
}

let changed_packages = change_mapper.changed_packages(changed_files.clone(), None);
let changed_packages = change_mapper
.changed_packages(changed_files.clone(), LockfileContents::Unchanged);

tracing::warn!("changed_files: {:?}", changed_files);
tracing::warn!("changed_packages: {:?}", changed_packages);
Expand Down
23 changes: 10 additions & 13 deletions crates/turborepo-lib/src/run/scope/change_detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use turbopath::{AbsoluteSystemPath, AnchoredSystemPathBuf};
use turborepo_repository::{
change_mapper::{
AllPackageChangeReason, ChangeMapper, DefaultPackageChangeMapper, Error,
GlobalDepsPackageChangeMapper, PackageChanges, PackageInclusionReason,
GlobalDepsPackageChangeMapper, LockfileContents, PackageChanges, PackageInclusionReason,
},
package_graph::{PackageGraph, PackageName},
};
Expand Down Expand Up @@ -52,13 +52,12 @@ impl<'a> ScopeChangeDetector<'a> {
}

/// Gets the lockfile content from SCM if it has changed.
/// Does *not* error if cannot get content, instead just
/// returns a Some(None)
fn get_lockfile_contents(
/// Does *not* error if cannot get content.
pub fn get_lockfile_contents(
&self,
from_ref: Option<&str>,
changed_files: &HashSet<AnchoredSystemPathBuf>,
) -> Option<Option<Vec<u8>>> {
) -> LockfileContents {
let lockfile_path = self
.pkg_graph
.package_manager()
Expand All @@ -70,23 +69,21 @@ impl<'a> ScopeChangeDetector<'a> {
&lockfile_path,
) {
debug!("lockfile did not change");
return None;
return LockfileContents::Unchanged;
}

let lockfile_path = self
.pkg_graph
.package_manager()
.lockfile_path(self.turbo_root);

let Ok(content) = self.scm.previous_content(from_ref, &lockfile_path) else {
return Some(None);
debug!("lockfile did change but could not get previous content");
return LockfileContents::UnknownChange;
};

Some(Some(content))
debug!("lockfile changed, have the previous content");
LockfileContents::Changed(content)
}
}

impl<'a> GitChangeDetector for ScopeChangeDetector<'a> {
/// get the actual changed packages between two git refs
fn changed_packages(
&self,
from_ref: Option<&str>,
Expand Down
1 change: 1 addition & 0 deletions crates/turborepo-repository/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ biome_diagnostics = { workspace = true }
biome_json_parser = { workspace = true }
biome_json_syntax = { workspace = true }

either = { workspace = true }
globwalk = { version = "0.1.0", path = "../turborepo-globwalk" }
itertools = { workspace = true }
lazy-regex = "2.5.0"
Expand Down
47 changes: 32 additions & 15 deletions crates/turborepo-repository/src/change_mapper/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use std::{
};

pub use package::{
DefaultPackageChangeMapper, Error, GlobalDepsPackageChangeMapper, PackageChangeMapper,
PackageMapping,
DefaultPackageChangeMapper, DefaultPackageChangeMapperWithLockfile, Error,
GlobalDepsPackageChangeMapper, PackageChangeMapper, PackageMapping,
};
use tracing::debug;
use turbopath::{AbsoluteSystemPath, AnchoredSystemPathBuf};
Expand All @@ -29,6 +29,19 @@ pub enum LockfileChange {
ChangedPackages(HashSet<turborepo_lockfiles::Package>),
}

/// This describes the state of a change to a lockfile.
pub enum LockfileContents {
/// We know the lockfile did not change
Unchanged,
/// We know the lockfile changed but don't have the file contents of the
/// previous lockfile (i.e. `git status`, or perhaps a lockfile that was
/// deleted or otherwise inaccessible with the information we have)
UnknownChange,
/// We know the lockfile changed and have the contents of the previous
/// lockfile
Changed(Vec<u8>),
}

#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum PackageInclusionReason {
/// All the packages are invalidated
Expand Down Expand Up @@ -122,13 +135,7 @@ impl<'a, PD: PackageChangeMapper> ChangeMapper<'a, PD> {
pub fn changed_packages(
&self,
changed_files: HashSet<AnchoredSystemPathBuf>,
// None - we don't know if the lockfile changed
//
// Some(None) - we know the lockfile changed, but don't know exactly why (i.e. `git status`
// and the lockfile is there)
//
// Some(Some(content)) - we know the lockfile changed and have the contents
lockfile_change: Option<Option<Vec<u8>>>,
lockfile_contents: LockfileContents,
) -> Result<PackageChanges, ChangeMapError> {
if let Some(file) = Self::default_global_file_changed(&changed_files) {
debug!("global file changed");
Expand All @@ -142,15 +149,16 @@ impl<'a, PD: PackageChangeMapper> ChangeMapper<'a, PD> {
// get filtered files and add the packages that contain them
let filtered_changed_files = self.filter_ignored_files(changed_files.iter())?;

// calculate lockfile_change here based on changed_files
match self.get_changed_packages(filtered_changed_files.into_iter()) {
PackageChanges::All(reason) => Ok(PackageChanges::All(reason)),

PackageChanges::Some(mut changed_pkgs) => {
match lockfile_change {
Some(Some(content)) => {
match lockfile_contents {
LockfileContents::Changed(previous_lockfile_contents) => {
// if we run into issues, don't error, just assume all packages have changed
let Ok(lockfile_changes) =
self.get_changed_packages_from_lockfile(&content)
self.get_changed_packages_from_lockfile(&previous_lockfile_contents)
else {
debug!(
"unable to determine lockfile changes, assuming all packages \
Expand Down Expand Up @@ -183,13 +191,22 @@ impl<'a, PD: PackageChangeMapper> ChangeMapper<'a, PD> {
}

// We don't have the actual contents, so just invalidate everything
Some(None) => {
debug!("no previous lockfile available, assuming all packages changed");
LockfileContents::UnknownChange => {
// this can happen in a blobless checkout
debug!(
"we know the lockfile changed but we don't have the contents so we \
have to assume all packages changed and rebuild everything"
);
Ok(PackageChanges::All(
AllPackageChangeReason::LockfileChangedWithoutDetails,
))
}
None => Ok(PackageChanges::Some(changed_pkgs)),

// We don't know if the lockfile changed or not, so we can't assume anything
LockfileContents::Unchanged => {
debug!("the lockfile did not change");
Ok(PackageChanges::Some(changed_pkgs))
}
}
}
}
Expand Down
82 changes: 58 additions & 24 deletions crates/turborepo-repository/src/change_mapper/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ pub trait PackageChangeMapper {
fn detect_package(&self, file: &AnchoredSystemPath) -> PackageMapping;
}

impl<L, R> PackageChangeMapper for either::Either<L, R>
where
L: PackageChangeMapper,
R: PackageChangeMapper,
{
fn detect_package(&self, file: &AnchoredSystemPath) -> PackageMapping {
match self {
either::Either::Left(l) => l.detect_package(file),
either::Either::Right(r) => r.detect_package(file),
}
}
}

/// Detects package by checking if the file is inside the package.
///
/// Does *not* use the `globalDependencies` in turbo.json.
Expand Down Expand Up @@ -73,6 +86,43 @@ impl PackageChangeMapper for DefaultPackageChangeMapper<'_> {
}
}

pub struct DefaultPackageChangeMapperWithLockfile<'a> {
base: DefaultPackageChangeMapper<'a>,
}

impl<'a> DefaultPackageChangeMapperWithLockfile<'a> {
pub fn new(pkg_dep_graph: &'a PackageGraph) -> Self {
Self {
base: DefaultPackageChangeMapper::new(pkg_dep_graph),
}
}
}

impl PackageChangeMapper for DefaultPackageChangeMapperWithLockfile<'_> {
fn detect_package(&self, path: &AnchoredSystemPath) -> PackageMapping {
// If we have a lockfile change, we consider this as a root package change,
// since there's a chance that the root package uses a workspace package
// dependency (this is cursed behavior but sadly possible). There's a chance
// that we can make this more accurate by checking which package
// manager, since not all package managers may permit root pulling from
// workspace package dependencies
if PackageManager::supported_managers()
.iter()
.any(|pm| pm.lockfile_name() == path.as_str())
{
PackageMapping::Package((
WorkspacePackage {
name: PackageName::Root,
path: AnchoredSystemPathBuf::from_raw("").unwrap(),
},
PackageInclusionReason::ConservativeRootLockfileChanged,
))
} else {
self.base.detect_package(path)
}
}
}

#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
Expand All @@ -88,7 +138,7 @@ pub enum Error {
/// changes all packages. Since we have a list of global deps,
/// we can check against that and avoid invalidating in unnecessary cases.
pub struct GlobalDepsPackageChangeMapper<'a> {
pkg_dep_graph: &'a PackageGraph,
base: DefaultPackageChangeMapperWithLockfile<'a>,
global_deps_matcher: wax::Any<'a>,
}

Expand All @@ -97,36 +147,19 @@ impl<'a> GlobalDepsPackageChangeMapper<'a> {
pkg_dep_graph: &'a PackageGraph,
global_deps: I,
) -> Result<Self, Error> {
let base = DefaultPackageChangeMapperWithLockfile::new(pkg_dep_graph);
let global_deps_matcher = wax::any(global_deps)?;

Ok(Self {
pkg_dep_graph,
base,
global_deps_matcher,
})
}
}

impl PackageChangeMapper for GlobalDepsPackageChangeMapper<'_> {
fn detect_package(&self, path: &AnchoredSystemPath) -> PackageMapping {
// If we have a lockfile change, we consider this as a root package change,
// since there's a chance that the root package uses a workspace package
// dependency (this is cursed behavior but sadly possible). There's a chance
// that we can make this more accurate by checking which package
// manager, since not all package managers may permit root pulling from
// workspace package dependencies
if PackageManager::supported_managers()
.iter()
.any(|pm| pm.lockfile_name() == path.as_str())
{
return PackageMapping::Package((
WorkspacePackage {
name: PackageName::Root,
path: AnchoredSystemPathBuf::from_raw("").unwrap(),
},
PackageInclusionReason::ConservativeRootLockfileChanged,
));
}
match DefaultPackageChangeMapper::new(self.pkg_dep_graph).detect_package(path) {
match self.base.detect_package(path) {
// Since `DefaultPackageChangeMapper` is overly conservative, we can check here if
// the path is actually in globalDeps and if not, return it as
// PackageDetection::Package(WorkspacePackage::root()).
Expand Down Expand Up @@ -160,7 +193,8 @@ mod tests {
use super::{DefaultPackageChangeMapper, GlobalDepsPackageChangeMapper};
use crate::{
change_mapper::{
AllPackageChangeReason, ChangeMapper, PackageChanges, PackageInclusionReason,
AllPackageChangeReason, ChangeMapper, LockfileContents, PackageChanges,
PackageInclusionReason,
},
discovery::{self, PackageDiscovery},
package_graph::{PackageGraphBuilder, WorkspacePackage},
Expand Down Expand Up @@ -208,7 +242,7 @@ mod tests {
[AnchoredSystemPathBuf::from_raw("README.md")?]
.into_iter()
.collect(),
None,
LockfileContents::Unchanged,
)?;

// We should return All because we don't have global deps and
Expand All @@ -228,7 +262,7 @@ mod tests {
[AnchoredSystemPathBuf::from_raw("README.md")?]
.into_iter()
.collect(),
None,
LockfileContents::Unchanged,
)?;

// We only get a root workspace change since we have global deps specified and
Expand Down
1 change: 1 addition & 0 deletions crates/turborepo-scm/src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ impl SCM {
}
}

/// get the actual changed files between two git refs
pub fn changed_files(
&self,
turbo_root: &AbsoluteSystemPath,
Expand Down
Loading
Loading