Skip to content
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

fix: allow for workplace descriptors without a protocol #4755

Merged
merged 1 commit into from May 1, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,33 @@
# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!

__metadata:
version: 6

"a@workspace:packages/a":
version: 0.0.0-use.local
resolution: "a@workspace:packages/a"
dependencies:
c: "*"
languageName: unknown
linkType: soft

"b@workspace:packages/b":
version: 0.0.0-use.local
resolution: "b@workspace:packages/b"
dependencies:
c: "workspace:*"
languageName: unknown
linkType: soft

"c@*, c@workspace:*, c@workspace:packages/c":
version: 0.0.0-use.local
resolution: "c@workspace:packages/c"
languageName: unknown
linkType: soft

"prune-edge@workspace:.":
version: 0.0.0-use.local
resolution: "prune-edge@workspace:."
languageName: unknown
linkType: soft
50 changes: 45 additions & 5 deletions crates/turborepo-lockfiles/src/berry/mod.rs
Expand Up @@ -266,11 +266,10 @@ impl<'a> BerryLockfile<'a> {
resolutions.insert(dependency, dep_locator.clone());
}

if let Some(descriptors) = reverse_lookup.get(locator) {
for descriptor in descriptors {
resolutions.insert((*descriptor).clone(), locator.clone());
}
}
// Included workspaces will always have their locator listed as a descriptor.
// All other descriptors should show up in the other workspace package
// dependencies.
resolutions.insert(Descriptor::from(locator.clone()), locator.clone());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"c@*, c@workspace:*, c@workspace:packages/c":

Am I understanding correctly that this whole line is the "descriptor" and the "locator" is the packages/c part?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda: c@*, c@workspace:*, and c@workspace:packages/c are all descriptors and then they all get concat'd to be a key in the YAML lockfile. c@workspace:packages/c is also a locator in this lockfile which appears in the resolution field of the lockfile entry.

}
}

Expand Down Expand Up @@ -779,4 +778,45 @@ mod test {
version: "4.4.1".into()
}));
}

#[test]
fn test_workspace_collission() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn test_workspace_collission() {
fn test_workspace_collision() {

let data = LockfileData::from_bytes(include_bytes!(
"../../fixtures/berry-protocol-collision.lock"
))
.unwrap();
let lockfile = BerryLockfile::new(&data, None).unwrap();
let no_proto = Descriptor::try_from("c@*").unwrap();
let workspace_proto = Descriptor::try_from("c@workspace:*").unwrap();
let full_path = Descriptor::try_from("c@workspace:packages/c").unwrap();
let a_lockfile = lockfile
.subgraph(&["packages/a".into(), "packages/c".into()], &[])
.unwrap();
let a_reverse_lookup = a_lockfile.locator_to_descriptors();
let a_c_descriptors = a_reverse_lookup
.get(&Locator::try_from("c@workspace:packages/c").unwrap())
.unwrap();

assert_eq!(
a_c_descriptors,
&(vec![&no_proto, &full_path]
.into_iter()
.collect::<HashSet<_>>())
);

let b_lockfile = lockfile
.subgraph(&["packages/b".into(), "packages/c".into()], &[])
.unwrap();
let b_reverse_lookup = b_lockfile.locator_to_descriptors();
let b_c_descriptors = b_reverse_lookup
.get(&Locator::try_from("c@workspace:packages/c").unwrap())
.unwrap();

assert_eq!(
b_c_descriptors,
&(vec![&workspace_proto, &full_path]
.into_iter()
.collect::<HashSet<_>>())
);
}
}
38 changes: 35 additions & 3 deletions crates/turborepo-lockfiles/src/berry/protocol_resolver.rs
Expand Up @@ -5,7 +5,7 @@ use super::identifiers::{Descriptor, Ident};
/// A data structure for resolving descriptors when the protocol isn't known
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct DescriptorResolver<'a> {
mapping: HashMap<Key<'a>, &'a str>,
mapping: HashMap<Key<'a>, Entry<'a>>,
}

#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
Expand All @@ -14,18 +14,25 @@ struct Key<'a> {
range: &'a str,
}

#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
struct Entry<'a> {
without: Option<&'a str>,
with: Option<&'a str>,
}

impl<'a> DescriptorResolver<'a> {
/// Add a descriptor to the resolver
pub fn insert(&mut self, descriptor: &Descriptor<'a>) -> Option<&'a str> {
let key = Key::new(descriptor)?;
self.mapping.insert(key, descriptor.range()?)
let entry = self.mapping.entry(key).or_default();
entry.insert_descriptor(descriptor)
}

/// If given a descriptor without a protocol it will return all matching
/// descriptors with a protocol
pub fn get(&self, descriptor: &Descriptor) -> Option<&'a str> {
let key = Key::new(descriptor)?;
self.mapping.get(&key).copied()
self.mapping.get(&key).and_then(|e| e.get(descriptor))
}
}

Expand All @@ -37,6 +44,28 @@ impl<'a> Key<'a> {
}
}

impl<'a> Entry<'a> {
// Insert the given descriptor's range into the correct slot depending if it is
// with or without a protocol
fn insert_descriptor(&mut self, descriptor: &Descriptor<'a>) -> Option<&'a str> {
let range = descriptor.range()?;
match descriptor.protocol().is_some() {
true => self.with.replace(range),
false => self.without.replace(range),
}
}

fn get(&self, descriptor: &Descriptor) -> Option<&'a str> {
// We only return the without protocol range if `without` is present
// and the given descriptor is also without a protocol
if self.without.is_some() && descriptor.protocol().is_none() {
self.without
} else {
self.with
}
}
}

#[cfg(test)]
mod test {
use super::*;
Expand All @@ -58,7 +87,10 @@ mod test {
fn test_descriptors_without_protocols() {
let mut resolver = DescriptorResolver::default();
let workspace = Descriptor::new("internal-workspace", "*").unwrap();
let workspace_with_protocol = Descriptor::new("internal-workspace", "workspace:*").unwrap();
assert!(resolver.insert(&workspace).is_none());
assert!(resolver.insert(&workspace_with_protocol).is_none());
assert_eq!(resolver.get(&workspace), Some("*"));
assert_eq!(resolver.get(&workspace_with_protocol), Some("workspace:*"));
}
}