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

Intern PackageId #6332

Merged
merged 8 commits into from
Nov 26, 2018
Merged
Show file tree
Hide file tree
Changes from 5 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
121 changes: 81 additions & 40 deletions src/cargo/core/package_id.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::cmp::Ordering;
use std::collections::HashSet;
use std::fmt::{self, Formatter};
use std::hash;
use std::hash::Hash;
use std::path::Path;
use std::sync::Arc;
use std::ptr;
use std::sync::Mutex;

use semver;
use serde::de;
Expand All @@ -13,19 +14,41 @@ use core::interning::InternedString;
use core::source::SourceId;
use util::{CargoResult, ToSemver};

lazy_static! {
static ref PACKAGE_ID_CACHE: Mutex<HashSet<&'static PackageIdInner>> = Mutex::new(HashSet::new());
}

/// Identifier for a specific version of a package in a specific source.
#[derive(Clone)]
#[derive(Clone, Copy, Eq, Hash, PartialOrd, Ord)]
pub struct PackageId {
inner: Arc<PackageIdInner>,
inner: &'static PackageIdInner,
}

#[derive(PartialEq, PartialOrd, Eq, Ord)]
#[derive(PartialOrd, Eq, Ord)]
struct PackageIdInner {
name: InternedString,
version: semver::Version,
source_id: SourceId,
}

// Custom equality that uses full equality of SourceId, rather than its custom equality.
impl PartialEq for PackageIdInner {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
&& self.version == other.version
&& self.source_id.full_eq(&other.source_id)
}
}

// Custom hash that is coherent with the custom equality above.
impl Hash for PackageIdInner {
fn hash<S: hash::Hasher>(&self, into: &mut S) {
self.name.hash(into);
self.version.hash(into);
self.source_id.full_hash(into);
}
}

impl ser::Serialize for PackageId {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
Expand Down Expand Up @@ -64,51 +87,46 @@ impl<'de> de::Deserialize<'de> for PackageId {
};
let source_id = SourceId::from_url(url).map_err(de::Error::custom)?;

Ok(PackageId {
inner: Arc::new(PackageIdInner {
Ok(PackageId::wrap(
PackageIdInner {
name: InternedString::new(name),
version,
source_id,
}),
})
}
}

impl Hash for PackageId {
fn hash<S: hash::Hasher>(&self, state: &mut S) {
self.inner.name.hash(state);
self.inner.version.hash(state);
self.inner.source_id.hash(state);
}
))
}
}

impl PartialEq for PackageId {
fn eq(&self, other: &PackageId) -> bool {
if ptr::eq(self.inner, other.inner) {
return true;
}
(*self.inner).eq(&*other.inner)
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
}
}
impl PartialOrd for PackageId {
fn partial_cmp(&self, other: &PackageId) -> Option<Ordering> {
(*self.inner).partial_cmp(&*other.inner)
}
}
impl Eq for PackageId {}
impl Ord for PackageId {
fn cmp(&self, other: &PackageId) -> Ordering {
(*self.inner).cmp(&*other.inner)
}
}

impl PackageId {
pub fn new<T: ToSemver>(name: &str, version: T, sid: SourceId) -> CargoResult<PackageId> {
let v = version.to_semver()?;
Ok(PackageId {
inner: Arc::new(PackageIdInner {

Ok(PackageId::wrap(
PackageIdInner {
name: InternedString::new(name),
version: v,
source_id: sid,
}),
})
}
))
}

fn wrap(inner: PackageIdInner) -> PackageId {
let mut cache = PACKAGE_ID_CACHE.lock().unwrap();
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
let inner = cache.get(&inner).map(|&x| x).unwrap_or_else(|| {
let inner = Box::leak(Box::new(inner));
cache.insert(inner);
inner
});
PackageId { inner }
}

pub fn name(&self) -> InternedString {
Expand All @@ -122,23 +140,23 @@ impl PackageId {
}

pub fn with_precise(&self, precise: Option<String>) -> PackageId {
PackageId {
inner: Arc::new(PackageIdInner {
PackageId::wrap(
PackageIdInner {
name: self.inner.name,
version: self.inner.version.clone(),
source_id: self.inner.source_id.with_precise(precise),
}),
}
}
)
}

pub fn with_source_id(&self, source: SourceId) -> PackageId {
PackageId {
inner: Arc::new(PackageIdInner {
PackageId::wrap(
PackageIdInner {
name: self.inner.name,
version: self.inner.version.clone(),
source_id: source,
}),
}
}
)
}

pub fn stable_hash<'a>(&'a self, workspace: &'a Path) -> PackageIdStableHash<'a> {
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -195,4 +213,27 @@ mod tests {
assert!(PackageId::new("foo", "bar", repo).is_err());
assert!(PackageId::new("foo", "", repo).is_err());
}

#[test]
fn debug() {
let loc = CRATES_IO_INDEX.to_url().unwrap();
let pkg_id = PackageId::new("foo", "1.0.0", SourceId::for_registry(&loc).unwrap()).unwrap();
assert_eq!(r#"PackageId { name: "foo", version: "1.0.0", source: "registry `https://github.com/rust-lang/crates.io-index`" }"#, format!("{:?}", pkg_id));

let pretty = r#"
PackageId {
name: "foo",
version: "1.0.0",
source: "registry `https://github.com/rust-lang/crates.io-index`"
}
"#.trim();
assert_eq!(pretty, format!("{:#?}", pkg_id));
}

#[test]
fn display() {
let loc = CRATES_IO_INDEX.to_url().unwrap();
let pkg_id = PackageId::new("foo", "1.0.0", SourceId::for_registry(&loc).unwrap()).unwrap();
assert_eq!("foo v1.0.0", pkg_id.to_string());
}
}
8 changes: 8 additions & 0 deletions src/cargo/core/source/source_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,14 @@ impl SourceId {
}
self.hash(into)
}

pub fn full_eq(&self, other: &SourceId) -> bool {
self.inner == other.inner
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
}

pub fn full_hash<S: hash::Hasher>(&self, into: &mut S) {
self.inner.hash(into)
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
}
}

impl PartialOrd for SourceId {
Expand Down
16 changes: 15 additions & 1 deletion tests/testsuite/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::thread;
use support::paths::{self, CargoPathExt};
use support::sleep_ms;
use support::{basic_lib_manifest, basic_manifest, git, main_file, path2url, project};
use support::Project;

#[test]
fn cargo_compile_simple_git_dep() {
Expand Down Expand Up @@ -1090,6 +1091,18 @@ fn two_deps_only_update_one() {
).file("src/main.rs", "fn main() {}")
.build();

fn oid_to_short_sha(oid: git2::Oid) -> String {
oid.to_string()[..8].to_string()
}
fn git_repo_head_sha(p: &Project) -> String {
let repo = git2::Repository::open(p.root()).unwrap();
let head = repo.head().unwrap().target().unwrap();
oid_to_short_sha(head)
}

println!("dep1 head sha: {}", git_repo_head_sha(&git1));
println!("dep2 head sha: {}", git_repo_head_sha(&git2));
dwijnand marked this conversation as resolved.
Show resolved Hide resolved

p.cargo("build")
.with_stderr(
"[UPDATING] git repository `[..]`\n\
Expand All @@ -1106,7 +1119,8 @@ fn two_deps_only_update_one() {
.unwrap();
let repo = git2::Repository::open(&git1.root()).unwrap();
git::add(&repo);
git::commit(&repo);
let oid = git::commit(&repo);
println!("dep1 head sha: {}", oid_to_short_sha(oid));

p.cargo("update -p dep1")
.with_stderr(&format!(
Expand Down