Skip to content

Commit

Permalink
Implement top-level overrides
Browse files Browse the repository at this point in the history
This commit is an implementation of top-level overrides to be encoded into the
manifest itself directly. This style of override is distinct from the existing
`paths` support in `.cargo/config` in two important ways:

* Top level overrides are intended to be checked in and shared amongst all
  developers of a project.
* Top level overrides are reflected in `Cargo.lock`.

The second point is crucially important here as it will ensure that an override
on one machine behaves the same as an override on another machine. This solves
many long-standing problems with `paths`-based overrides which suffer from some
level of nondeterminism as they're not encoded.

From a syntactical point of view, an override looks like:

```toml
[replace]
"libc:0.2.0" = { git = 'https://github.com/my-username/libc', branch = '0.2-fork' }
```

This declaration indicates that whenever resolution would otherwise encounter
the `libc` package version 0.2.0 from crates.io, it should instead replace it
with the custom git dependency on a specific branch.

The key "libc:0.2.0" here is actually a package id specification which will
allow selecting various components of a graph. For example the same named
package coming from two distinct locations can be selected against, as well as
multiple versions of one crate in a dependency graph. The replacement dependency
has the same syntax as the `[dependencies]` section of Cargo.toml.

One of the major uses of this syntax will be, for example, using a temporary
fork of a crate while the changes are pushed upstream to the original repo. This
will avoid the need to change the intermediate projects immediately, and over
time once fixes have landed upstream the `[replace]` section in a `Cargo.toml`
can be removed.

There are also two crucial restrictions on overrides.

* A crate with the name `foo` can only get overridden with packages also of
  the name `foo`.
* A crate can only get overridden with a crate of the exact same version.

A consequence of these restrictions is that crates.io cannot be used to replace
anything from crates.io. There's only one version of something on crates.io, so
there's nothing else to replace it with (name/version are a unique key).

Closes rust-lang#942
  • Loading branch information
alexcrichton committed Mar 25, 2016
1 parent 4e6434d commit 54d738b
Show file tree
Hide file tree
Showing 13 changed files with 878 additions and 155 deletions.
11 changes: 7 additions & 4 deletions src/cargo/core/manifest.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use std::default::Default;
use std::fmt;
use std::path::{PathBuf, Path};

use semver::Version;
use rustc_serialize::{Encoder, Encodable};

use core::{Dependency, PackageId, Summary};
use core::{Dependency, PackageId, PackageIdSpec, Summary};
use core::package_id::Metadata;
use util::{CargoResult, human};

Expand All @@ -20,7 +19,8 @@ pub struct Manifest {
include: Vec<String>,
metadata: ManifestMetadata,
profiles: Profiles,
publish: bool
publish: bool,
replace: Vec<(PackageIdSpec, Dependency)>,
}

/// General metadata about a package which is just blindly uploaded to the
Expand Down Expand Up @@ -165,7 +165,8 @@ impl Manifest {
links: Option<String>,
metadata: ManifestMetadata,
profiles: Profiles,
publish: bool) -> Manifest {
publish: bool,
replace: Vec<(PackageIdSpec, Dependency)>) -> Manifest {
Manifest {
summary: summary,
targets: targets,
Expand All @@ -176,6 +177,7 @@ impl Manifest {
metadata: metadata,
profiles: profiles,
publish: publish,
replace: replace,
}
}

Expand All @@ -191,6 +193,7 @@ impl Manifest {
pub fn warnings(&self) -> &[String] { &self.warnings }
pub fn profiles(&self) -> &Profiles { &self.profiles }
pub fn publish(&self) -> bool { self.publish }
pub fn replace(&self) -> &[(PackageIdSpec, Dependency)] { &self.replace }
pub fn links(&self) -> Option<&str> {
self.links.as_ref().map(|s| &s[..])
}
Expand Down
14 changes: 6 additions & 8 deletions src/cargo/core/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl<'cfg> PackageRegistry<'cfg> {
PackageRegistry {
sources: SourceMap::new(),
source_ids: HashMap::new(),
overrides: vec![],
overrides: Vec::new(),
config: config,
locked: HashMap::new(),
}
Expand Down Expand Up @@ -270,18 +270,16 @@ impl<'cfg> PackageRegistry<'cfg> {

impl<'cfg> Registry for PackageRegistry<'cfg> {
fn query(&mut self, dep: &Dependency) -> CargoResult<Vec<Summary>> {
let overrides = try!(self.query_overrides(dep));
let overrides = try!(self.query_overrides(&dep));

let ret = if overrides.is_empty() {
// Ensure the requested source_id is loaded
try!(self.ensure_loaded(dep.source_id(), Kind::Normal));
let mut ret = Vec::new();
for (id, src) in self.sources.sources_mut() {
if id == dep.source_id() {
ret.extend(try!(src.query(dep)).into_iter());
}

match self.sources.get_mut(dep.source_id()) {
Some(src) => try!(src.query(&dep)),
None => Vec::new(),
}
ret
} else {
overrides
};
Expand Down
64 changes: 43 additions & 21 deletions src/cargo/core/resolver/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,23 @@ impl EncodableResolve {

let mut g = Graph::new();
let mut tmp = HashMap::new();
let mut replacements = HashMap::new();

let packages = Vec::new();
let packages = self.package.as_ref().unwrap_or(&packages);

let root = try!(to_package_id(&self.root.name,
&self.root.version,
self.root.source.as_ref(),
default, &path_deps));
let ids = try!(packages.iter().map(|p| {
to_package_id(&p.name, &p.version, p.source.as_ref(),
let id2pkgid = |id: &EncodablePackageId| {
to_package_id(&id.name, &id.version, id.source.as_ref(),
default, &path_deps)
}).collect::<CargoResult<Vec<_>>>());
};
let dep2pkgid = |dep: &EncodableDependency| {
to_package_id(&dep.name, &dep.version, dep.source.as_ref(),
default, &path_deps)
};

let root = try!(dep2pkgid(&self.root));
let ids = try!(packages.iter().map(&dep2pkgid)
.collect::<CargoResult<Vec<_>>>());

{
let mut register_pkg = |pkgid: &PackageId| {
Expand All @@ -57,16 +62,22 @@ impl EncodableResolve {
{
let mut add_dependencies = |id: &PackageId, pkg: &EncodableDependency|
-> CargoResult<()> {
if let Some(ref replace) = pkg.replace {
let replace = try!(id2pkgid(replace));
let replace_precise = tmp.get(&replace).map(|p| {
replace.with_precise(p.clone())
}).unwrap_or(replace);
replacements.insert(id.clone(), replace_precise);
assert!(pkg.dependencies.is_none());
return Ok(())
}

let deps = match pkg.dependencies {
Some(ref deps) => deps,
None => return Ok(()),
};
for edge in deps.iter() {
let to_depend_on = try!(to_package_id(&edge.name,
&edge.version,
edge.source.as_ref(),
default,
&path_deps));
let to_depend_on = try!(id2pkgid(edge));
let precise_pkgid =
tmp.get(&to_depend_on)
.map(|p| to_depend_on.with_precise(p.clone()))
Expand All @@ -87,6 +98,7 @@ impl EncodableResolve {
root: root,
features: HashMap::new(),
metadata: self.metadata.clone(),
replacements: replacements,
})
}
}
Expand Down Expand Up @@ -136,7 +148,8 @@ pub struct EncodableDependency {
name: String,
version: String,
source: Option<SourceId>,
dependencies: Option<Vec<EncodablePackageId>>
dependencies: Option<Vec<EncodablePackageId>>,
replace: Option<EncodablePackageId>,
}

#[derive(Debug, PartialOrd, Ord, PartialEq, Eq)]
Expand Down Expand Up @@ -186,24 +199,32 @@ impl Encodable for Resolve {
let encodable = ids.iter().filter_map(|&id| {
if self.root == *id { return None; }

Some(encodable_resolve_node(id, &self.graph))
Some(encodable_resolve_node(id, self))
}).collect::<Vec<EncodableDependency>>();

EncodableResolve {
package: Some(encodable),
root: encodable_resolve_node(&self.root, &self.graph),
root: encodable_resolve_node(&self.root, self),
metadata: self.metadata.clone(),
}.encode(s)
}
}

fn encodable_resolve_node(id: &PackageId, graph: &Graph<PackageId>)
fn encodable_resolve_node(id: &PackageId, resolve: &Resolve)
-> EncodableDependency {
let deps = graph.edges(id).map(|edge| {
let mut deps = edge.map(encodable_package_id).collect::<Vec<_>>();
deps.sort();
deps
});
let (replace, deps) = match resolve.replacement(id) {
Some(id) => {
(Some(encodable_package_id(id)), None)
}
None => {
let mut deps = resolve.graph.edges(id)
.into_iter().flat_map(|a| a)
.map(encodable_package_id)
.collect::<Vec<_>>();
deps.sort();
(None, Some(deps))
}
};

let source = if id.source_id().is_path() {
None
Expand All @@ -216,6 +237,7 @@ fn encodable_resolve_node(id: &PackageId, graph: &Graph<PackageId>)
version: id.version().to_string(),
source: source,
dependencies: deps,
replace: replace,
}
}

Expand Down
Loading

0 comments on commit 54d738b

Please sign in to comment.