Skip to content

Commit a13767b

Browse files
committed
Refactor to support stabilized package ids
This refactors the internals, and the public API, to support both the current "opaque" package ids as well as the new package id format stabilized in <rust-lang/cargo#12914>
1 parent 3ee7d64 commit a13767b

31 files changed

+1403
-1157
lines changed

examples/graph.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub type Graph = krates::Krates<Simple>;
2828
impl From<krates::cm::Package> for Simple {
2929
fn from(pkg: krates::cm::Package) -> Self {
3030
Self {
31-
id: pkg.id,
31+
id: pkg.id.into(),
3232
//features: pkg.fee
3333
}
3434
}

src/builder.rs

Lines changed: 141 additions & 92 deletions
Large diffs are not rendered by default.

src/lib.rs

Lines changed: 225 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,162 @@ pub use builder::{
4848
};
4949
pub use errors::Error;
5050
pub use pkgspec::PkgSpec;
51+
use std::fmt;
5152

5253
/// A crate's unique identifier
53-
pub type Kid = cargo_metadata::PackageId;
54+
#[derive(Clone)]
55+
pub struct Kid {
56+
pub repr: String,
57+
// The subslices for each component in name -> version -> source order
58+
components: [(usize, usize); 3],
59+
}
60+
61+
impl Kid {
62+
#[inline]
63+
pub fn name(&self) -> &str {
64+
let (s, e) = self.components[0];
65+
&self.repr[s..e]
66+
}
67+
68+
#[inline]
69+
pub fn version(&self) -> &str {
70+
let (s, e) = self.components[1];
71+
&self.repr[s..e]
72+
}
73+
74+
#[inline]
75+
pub fn source(&self) -> &str {
76+
let (s, e) = self.components[2];
77+
&self.repr[s..e]
78+
}
79+
80+
#[inline]
81+
pub fn rev(&self) -> Option<&str> {
82+
let src = self.source();
83+
if src.starts_with("git+") {
84+
// In old style ids the rev with always be available, but new ones
85+
// only have it if the rev field was actually set, which is unfortunate
86+
if let Some((_, rev)) = src.split_once('#') {
87+
Some(rev)
88+
} else {
89+
src.split_once("?rev=").map(|(_, r)| r)
90+
}
91+
} else {
92+
None
93+
}
94+
}
95+
}
96+
97+
#[allow(clippy::fallible_impl_from)]
98+
impl From<cargo_metadata::PackageId> for Kid {
99+
fn from(pid: cargo_metadata::PackageId) -> Self {
100+
let repr = pid.repr;
101+
102+
let gen = || {
103+
let components = if repr.contains(' ') {
104+
let name = (0, repr.find(' ')?);
105+
let version = (name.1 + 1, repr[name.1 + 1..].find(' ')? + name.1 + 1);
106+
// Note we skip the open and close parentheses as they are superfluous
107+
// as every source has them, as well as not being present in the new
108+
// stabilized format
109+
let source = (version.1 + 2, repr.len() - 1);
110+
111+
[name, version, source]
112+
} else {
113+
let vmn = repr.rfind('#')?;
114+
let (name, version) = if let Some(split) = repr[vmn..].find('@') {
115+
((vmn + 1, vmn + split), (vmn + split + 1, repr.len()))
116+
} else {
117+
let begin = repr.rfind('/')? + 1;
118+
let end = if repr.starts_with("git+") {
119+
repr[begin..].find('?')? + begin
120+
} else {
121+
vmn
122+
};
123+
124+
((begin, end), (vmn + 1, repr.len()))
125+
};
126+
127+
[name, version, (0, vmn)]
128+
};
129+
130+
Some(components)
131+
};
132+
133+
if let Some(components) = gen() {
134+
Self { repr, components }
135+
} else {
136+
panic!("unable to parse package id '{repr}'");
137+
}
138+
}
139+
}
140+
141+
impl fmt::Debug for Kid {
142+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143+
let mut ds = f.debug_struct("Kid");
144+
145+
ds.field("name", &self.name())
146+
.field("version", &self.version());
147+
148+
let src = self.source();
149+
if src != "registry+https://github.com/rust-lang/crates.io-index" {
150+
ds.field("source", &src);
151+
}
152+
153+
ds.finish()
154+
}
155+
}
156+
157+
impl fmt::Display for Kid {
158+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159+
f.write_str(&self.repr)
160+
}
161+
}
162+
163+
impl Default for Kid {
164+
fn default() -> Self {
165+
Self {
166+
repr: String::new(),
167+
components: [(0, 0), (0, 0), (0, 0)],
168+
}
169+
}
170+
}
171+
172+
impl std::hash::Hash for Kid {
173+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
174+
state.write(self.repr.as_bytes());
175+
}
176+
}
177+
178+
impl Ord for Kid {
179+
fn cmp(&self, o: &Self) -> std::cmp::Ordering {
180+
let a = &self.repr;
181+
let b = &o.repr;
182+
183+
for (ar, br) in self.components.into_iter().zip(o.components.into_iter()) {
184+
let ord = a[ar.0..ar.1].cmp(&b[br.0..br.1]);
185+
if ord != std::cmp::Ordering::Equal {
186+
return ord;
187+
}
188+
}
189+
190+
std::cmp::Ordering::Equal
191+
}
192+
}
193+
194+
impl PartialOrd for Kid {
195+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
196+
Some(self.cmp(other))
197+
}
198+
}
199+
200+
impl Eq for Kid {}
201+
202+
impl PartialEq for Kid {
203+
fn eq(&self, other: &Self) -> bool {
204+
self.cmp(other) == std::cmp::Ordering::Equal
205+
}
206+
}
54207

55208
/// The set of features that have been enabled on a crate
56209
pub type EnabledFeatures = std::collections::BTreeSet<String>;
@@ -84,8 +237,6 @@ impl PartialEq<DK> for DepKind {
84237
}
85238
}
86239

87-
use std::fmt;
88-
89240
impl fmt::Display for DepKind {
90241
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91242
match self {
@@ -509,29 +660,36 @@ where
509660
///
510661
/// fn print(krates: &Krates, name: &str) {
511662
/// let req = VersionReq::parse("=0.2").unwrap();
512-
/// for vs in krates.search_matches(name, req.clone()).map(|(_, krate)| &krate.version) {
513-
/// println!("found version {} matching {}!", vs, req);
663+
/// for vs in krates.search_matches(name, req.clone()).map(|km| &km.krate.version) {
664+
/// println!("found version {vs} matching {req}!");
514665
/// }
515666
/// }
516667
/// ```
517668
pub fn search_matches(
518669
&self,
519670
name: impl Into<String>,
520671
req: semver::VersionReq,
521-
) -> impl Iterator<Item = (NodeId, &N)> {
672+
) -> impl Iterator<Item = KrateMatch<'_, N>> {
522673
let raw_nodes = &self.graph.raw_nodes()[0..self.krates_end];
523674

524675
let name = name.into();
525676

526-
raw_nodes.iter().enumerate().filter_map(move |(id, node)| {
527-
if let Node::Krate { krate, .. } = &node.weight {
528-
if krate.name() == name && req.matches(krate.version()) {
529-
return Some((NodeId::new(id), krate));
677+
raw_nodes
678+
.iter()
679+
.enumerate()
680+
.filter_map(move |(index, node)| {
681+
if let Node::Krate { krate, id, .. } = &node.weight {
682+
if krate.name() == name && req.matches(krate.version()) {
683+
return Some(KrateMatch {
684+
node_id: NodeId::new(index),
685+
krate,
686+
kid: id,
687+
});
688+
}
530689
}
531-
}
532690

533-
None
534-
})
691+
None
692+
})
535693
}
536694

537695
/// Get an iterator over all of the crates in the graph with the given name,
@@ -541,28 +699,44 @@ where
541699
/// use krates::Krates;
542700
///
543701
/// fn print_all_versions(krates: &Krates, name: &str) {
544-
/// for vs in krates.krates_by_name(name).map(|(_, krate)| &krate.version) {
545-
/// println!("found version {}", vs);
702+
/// for vs in krates.krates_by_name(name).map(|km| &km.krate.version) {
703+
/// println!("found version {vs}");
546704
/// }
547705
/// }
548706
/// ```
549-
pub fn krates_by_name(&self, name: impl Into<String>) -> impl Iterator<Item = (NodeId, &N)> {
707+
pub fn krates_by_name(
708+
&self,
709+
name: impl Into<String>,
710+
) -> impl Iterator<Item = KrateMatch<'_, N>> {
550711
let raw_nodes = &self.graph.raw_nodes()[0..self.krates_end];
551712

552713
let name = name.into();
553714

554-
raw_nodes.iter().enumerate().filter_map(move |(id, node)| {
555-
if let Node::Krate { krate, .. } = &node.weight {
556-
if krate.name() == name {
557-
return Some((NodeId::new(id), krate));
715+
raw_nodes
716+
.iter()
717+
.enumerate()
718+
.filter_map(move |(index, node)| {
719+
if let Node::Krate { krate, id, .. } = &node.weight {
720+
if krate.name() == name {
721+
return Some(KrateMatch {
722+
node_id: NodeId::new(index),
723+
krate,
724+
kid: id,
725+
});
726+
}
558727
}
559-
}
560728

561-
None
562-
})
729+
None
730+
})
563731
}
564732
}
565733

734+
pub struct KrateMatch<'graph, N> {
735+
pub krate: &'graph N,
736+
pub kid: &'graph Kid,
737+
pub node_id: NodeId,
738+
}
739+
566740
impl<N, E> std::ops::Index<NodeId> for Krates<N, E> {
567741
type Output = N;
568742

@@ -586,3 +760,31 @@ impl<N, E> std::ops::Index<usize> for Krates<N, E> {
586760
}
587761
}
588762
}
763+
764+
#[cfg(test)]
765+
mod tests {
766+
#[test]
767+
fn converts_package_ids() {
768+
let ids = [
769+
("registry+https://github.com/rust-lang/crates.io-index#[email protected]", "ab_glyph", "0.2.22", "registry+https://github.com/rust-lang/crates.io-index", None),
770+
("git+https://github.com/EmbarkStudios/egui-stylist?rev=3900e8aedc5801e42c1bb747cfd025615bf3b832#0.2.0", "egui-stylist", "0.2.0", "git+https://github.com/EmbarkStudios/egui-stylist?rev=3900e8aedc5801e42c1bb747cfd025615bf3b832", Some("3900e8aedc5801e42c1bb747cfd025615bf3b832")),
771+
("path+file:///home/jake/code/ark/components/allocator#[email protected]", "ark-allocator", "0.1.0", "path+file:///home/jake/code/ark/components/allocator", None),
772+
("git+https://github.com/EmbarkStudios/ash?branch=nv-low-latency2#0.38.0+1.3.269", "ash", "0.38.0+1.3.269", "git+https://github.com/EmbarkStudios/ash?branch=nv-low-latency2", None),
773+
("git+https://github.com/EmbarkStudios/fsr-rs?branch=nv-low-latency2#[email protected]", "fsr", "0.1.7", "git+https://github.com/EmbarkStudios/fsr-rs?branch=nv-low-latency2", None),
774+
("fuser 0.4.1 (git+https://github.com/cberner/fuser?branch=master#b2e7622942e52a28ffa85cdaf48e28e982bb6923)", "fuser", "0.4.1", "git+https://github.com/cberner/fuser?branch=master#b2e7622942e52a28ffa85cdaf48e28e982bb6923", Some("b2e7622942e52a28ffa85cdaf48e28e982bb6923")),
775+
("a 0.1.0 (path+file:///home/jake/code/krates/tests/ws/a)", "a", "0.1.0", "path+file:///home/jake/code/krates/tests/ws/a", None),
776+
("bindgen 0.59.2 (registry+https://github.com/rust-lang/crates.io-index)", "bindgen", "0.59.2", "registry+https://github.com/rust-lang/crates.io-index", None),
777+
];
778+
779+
for (repr, name, version, source, rev) in ids {
780+
let kid = super::Kid::from(cargo_metadata::PackageId {
781+
repr: repr.to_owned(),
782+
});
783+
784+
assert_eq!(kid.name(), name);
785+
assert_eq!(kid.version(), version);
786+
assert_eq!(kid.source(), source);
787+
assert_eq!(kid.rev(), rev);
788+
}
789+
}
790+
}

src/pkgspec.rs

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,36 +23,18 @@ impl PkgSpec {
2323
}
2424
}
2525

26-
match self.url {
27-
Some(ref u) => {
28-
// Get the url from the identifier to avoid pointless
29-
// allocations.
30-
if let Some(mut url) = krate.id.repr.splitn(3, ' ').nth(2) {
31-
// Strip off the the enclosing parens
32-
url = &url[1..url.len() - 1];
33-
34-
// Strip off the leading <source>+
35-
if let Some(ind) = url.find('+') {
36-
url = &url[ind + 1..];
37-
}
38-
39-
// Strip off any fragments
40-
if let Some(ind) = url.rfind('#') {
41-
url = &url[..ind];
42-
}
26+
let Some((url, src)) = self
27+
.url
28+
.as_ref()
29+
.zip(krate.source.as_ref().map(|s| s.repr.as_str()))
30+
else {
31+
return true;
32+
};
4333

44-
// Strip off any query parts
45-
if let Some(ind) = url.rfind('?') {
46-
url = &url[..ind];
47-
}
34+
let begin = src.find('+').map_or(0, |i| i + 1);
35+
let end = src.find('?').or_else(|| src.find('#')).unwrap_or(src.len());
4836

49-
u == url
50-
} else {
51-
false
52-
}
53-
}
54-
None => true,
55-
}
37+
url == &src[begin..end]
5638
}
5739
}
5840

tests/features.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,13 @@ fn prunes_mixed_dependencies() {
4646

4747
macro_rules! assert_features {
4848
($graph:expr, $name:expr, $features:expr) => {
49-
let (_, krate) = $graph.krates_by_name($name).next().unwrap();
49+
let krates::KrateMatch { kid, .. } = $graph.krates_by_name($name).next().unwrap();
5050

5151
let expected_features: std::collections::BTreeSet<_> =
5252
$features.into_iter().map(|s| s.to_owned()).collect();
5353

5454
assert_eq!(
55-
$graph.get_enabled_features(&krate.id).unwrap(),
55+
$graph.get_enabled_features(kid).unwrap(),
5656
&expected_features
5757
);
5858
};

0 commit comments

Comments
 (0)