Skip to content

Commit 1389b33

Browse files
committed
Auto merge of #3369 - joshtriplett:cargo-install-only-required-dependencies, r=alexcrichton
cargo fails if it can't find optional dependencies, even if corresponding feature not enabled I have a directory registry containing all the crate sources needed to build an application crate (for instance, ripgrep), and a `$CARGO_HOME/config` file that looks like this: ```toml [source.crates-io] replace-with = "dh-cargo-registry" [source.dh-cargo-registry] directory = "/usr/share/cargo/registry/" ``` When I attempt to build ripgrep via "cargo install ripgrep" from that directory registry, I get this error: ``` error: failed to compile `ripgrep v0.3.1`, intermediate artifacts can be found at `/tmp/cargo-install.rmKApOw9BwAL` Caused by: no matching package named `simd` found (required by `bytecount`) location searched: registry https://github.com/rust-lang/crates.io-index version required: ^0.1.1 ``` The directory registry indeed does not contain "simd"; however, bytecount doesn't require simd. It has an optional dependency on simd, and nothing enables the feature that requires that dependency. Placing the simd crate sources into the directory registry allows ripgrep to build; the resulting build does not actually build the simd crate. I can reproduce this by just trying to build the "bytecount" crate directly, using the same `$CARGO_HOME`: ``` error: no matching package named `simd` found (required by `bytecount`) location searched: registry https://github.com/rust-lang/crates.io-index version required: = 0.1.1 ``` (Incidentally, that "version required" seems wrong: bytecount has an optional dependency on simd `^0.1.1`, not `=0.1.1`.) However, this doesn't seem consistent with other crates in the same dependency tree. For instance, ripgrep also depends on clap, and clap has an optional dependency on yaml-rust, yet cargo does not complain about the missing yaml-rust. I'd *guess* that the difference occurs because ripgrep has an optional feature `simd-accel` that depends on `bytecount/simd-accel`, so cargo wants to compute what packages it needs for that case too, even when building without that feature. (Similar to #3233.) However, this makes it impossible to build a package while installing only the packaged dependencies for the enabled features. Could `cargo install` ignore any dependencies not actually required by the enabled feature? (That behavior would make no sense for "cargo build", which builds a Cargo.lock file that should remain consistent regardless of enabled features, but it makes sense for "cargo install cratename", which doesn't build a Cargo.lock file.)
2 parents 3bc8865 + db71d87 commit 1389b33

File tree

5 files changed

+170
-16
lines changed

5 files changed

+170
-16
lines changed

src/cargo/core/workspace.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ pub struct Workspace<'cfg> {
4444
// True, if this is a temporary workspace created for the purposes of
4545
// cargo install or cargo package.
4646
is_ephemeral: bool,
47+
48+
// True if this workspace should enforce optional dependencies even when
49+
// not needed; false if this workspace should only enforce dependencies
50+
// needed by the current configuration (such as in cargo install).
51+
require_optional_deps: bool,
4752
}
4853

4954
// Separate structure for tracking loaded packages (to avoid loading anything
@@ -99,6 +104,7 @@ impl<'cfg> Workspace<'cfg> {
99104
target_dir: target_dir,
100105
members: Vec::new(),
101106
is_ephemeral: false,
107+
require_optional_deps: true,
102108
};
103109
ws.root_manifest = ws.find_root(manifest_path)?;
104110
ws.find_members()?;
@@ -115,8 +121,8 @@ impl<'cfg> Workspace<'cfg> {
115121
///
116122
/// This is currently only used in niche situations like `cargo install` or
117123
/// `cargo package`.
118-
pub fn ephemeral(package: Package, config: &'cfg Config, target_dir: Option<Filesystem>)
119-
-> CargoResult<Workspace<'cfg>> {
124+
pub fn ephemeral(package: Package, config: &'cfg Config, target_dir: Option<Filesystem>,
125+
require_optional_deps: bool) -> CargoResult<Workspace<'cfg>> {
120126
let mut ws = Workspace {
121127
config: config,
122128
current_manifest: package.manifest_path().to_path_buf(),
@@ -128,6 +134,7 @@ impl<'cfg> Workspace<'cfg> {
128134
target_dir: None,
129135
members: Vec::new(),
130136
is_ephemeral: true,
137+
require_optional_deps: require_optional_deps,
131138
};
132139
{
133140
let key = ws.current_manifest.parent().unwrap();
@@ -219,6 +226,10 @@ impl<'cfg> Workspace<'cfg> {
219226
self.is_ephemeral
220227
}
221228

229+
pub fn require_optional_deps(&self) -> bool {
230+
self.require_optional_deps
231+
}
232+
222233
/// Finds the root of a workspace for the crate whose manifest is located
223234
/// at `manifest_path`.
224235
///

src/cargo/ops/cargo_install.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ pub fn install(root: Option<&str>,
9797
};
9898

9999
let ws = match overidden_target_dir {
100-
Some(dir) => Workspace::ephemeral(pkg, config, Some(dir))?,
100+
Some(dir) => Workspace::ephemeral(pkg, config, Some(dir), false)?,
101101
None => Workspace::new(pkg.manifest_path(), config)?,
102102
};
103103
let pkg = ws.current()?;

src/cargo/ops/cargo_package.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ fn run_verify(ws: &Workspace, tar: &File, opts: &PackageOpts) -> CargoResult<()>
284284
let new_pkg = Package::new(new_manifest, &manifest_path);
285285

286286
// Now that we've rewritten all our path dependencies, compile it!
287-
let ws = Workspace::ephemeral(new_pkg, config, None)?;
287+
let ws = Workspace::ephemeral(new_pkg, config, None, true)?;
288288
ops::compile_ws(&ws, None, &ops::CompileOptions {
289289
config: config,
290290
jobs: opts.jobs,

src/cargo/ops/resolve.rs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,22 @@ pub fn resolve_ws_precisely<'a>(ws: &Workspace<'a>,
3737
registry.add_preloaded(source);
3838
}
3939

40-
// First, resolve the root_package's *listed* dependencies, as well as
41-
// downloading and updating all remotes and such.
42-
let resolve = resolve_with_registry(ws, &mut registry)?;
40+
let resolve = if ws.require_optional_deps() {
41+
// First, resolve the root_package's *listed* dependencies, as well as
42+
// downloading and updating all remotes and such.
43+
let resolve = resolve_with_registry(ws, &mut registry)?;
44+
45+
// Second, resolve with precisely what we're doing. Filter out
46+
// transitive dependencies if necessary, specify features, handle
47+
// overrides, etc.
48+
let _p = profile::start("resolving w/ overrides...");
4349

44-
// Second, resolve with precisely what we're doing. Filter out
45-
// transitive dependencies if necessary, specify features, handle
46-
// overrides, etc.
47-
let _p = profile::start("resolving w/ overrides...");
50+
add_overrides(&mut registry, ws)?;
4851

49-
add_overrides(&mut registry, ws)?;
52+
Some(resolve)
53+
} else {
54+
None
55+
};
5056

5157
let method = if all_features {
5258
Method::Everything
@@ -60,7 +66,7 @@ pub fn resolve_ws_precisely<'a>(ws: &Workspace<'a>,
6066

6167
let resolved_with_overrides =
6268
ops::resolve_with_previous(&mut registry, ws,
63-
method, Some(&resolve), None,
69+
method, resolve.as_ref(), None,
6470
specs)?;
6571

6672
for &(ref replace_spec, _) in ws.root_replace() {
@@ -159,8 +165,7 @@ pub fn resolve_with_previous<'a>(registry: &mut PackageRegistry,
159165
// members in the workspace, so propagate the `Method::Everything`.
160166
Method::Everything => Method::Everything,
161167

162-
// If we're not resolving everything though then the workspace is
163-
// already resolved and now we're drilling down from that to the
168+
// If we're not resolving everything though then we're constructing the
164169
// exact crate graph we're going to build. Here we don't necessarily
165170
// want to keep around all workspace crates as they may not all be
166171
// built/tested.
@@ -176,7 +181,6 @@ pub fn resolve_with_previous<'a>(registry: &mut PackageRegistry,
176181
// base method with no features specified but using default features
177182
// for any other packages specified with `-p`.
178183
Method::Required { dev_deps, .. } => {
179-
assert!(previous.is_some());
180184
let base = Method::Required {
181185
dev_deps: dev_deps,
182186
features: &[],

tests/directory.rs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::str;
1010

1111
use rustc_serialize::json;
1212

13+
use cargotest::cargo_process;
1314
use cargotest::support::{project, execs, ProjectBuilder};
1415
use cargotest::support::paths;
1516
use cargotest::support::registry::{Package, cksum};
@@ -104,6 +105,144 @@ fn simple() {
104105
"));
105106
}
106107

108+
#[test]
109+
fn simple_install() {
110+
setup();
111+
112+
VendorPackage::new("foo")
113+
.file("Cargo.toml", r#"
114+
[package]
115+
name = "foo"
116+
version = "0.1.0"
117+
authors = []
118+
"#)
119+
.file("src/lib.rs", "pub fn foo() {}")
120+
.build();
121+
122+
VendorPackage::new("bar")
123+
.file("Cargo.toml", r#"
124+
[package]
125+
name = "bar"
126+
version = "0.1.0"
127+
authors = []
128+
129+
[dependencies]
130+
foo = "0.1.0"
131+
"#)
132+
.file("src/main.rs", r#"
133+
extern crate foo;
134+
135+
pub fn main() {
136+
foo::foo();
137+
}
138+
"#)
139+
.build();
140+
141+
assert_that(cargo_process().arg("install").arg("bar"),
142+
execs().with_status(0).with_stderr(
143+
" Installing bar v0.1.0
144+
Compiling foo v0.1.0
145+
Compiling bar v0.1.0
146+
Finished release [optimized] target(s) in [..] secs
147+
Installing [..]bar[..]
148+
warning: be sure to add `[..]` to your PATH to be able to run the installed binaries
149+
"));
150+
}
151+
152+
#[test]
153+
fn simple_install_fail() {
154+
setup();
155+
156+
VendorPackage::new("foo")
157+
.file("Cargo.toml", r#"
158+
[package]
159+
name = "foo"
160+
version = "0.1.0"
161+
authors = []
162+
"#)
163+
.file("src/lib.rs", "pub fn foo() {}")
164+
.build();
165+
166+
VendorPackage::new("bar")
167+
.file("Cargo.toml", r#"
168+
[package]
169+
name = "bar"
170+
version = "0.1.0"
171+
authors = []
172+
173+
[dependencies]
174+
foo = "0.1.0"
175+
baz = "9.8.7"
176+
"#)
177+
.file("src/main.rs", r#"
178+
extern crate foo;
179+
180+
pub fn main() {
181+
foo::foo();
182+
}
183+
"#)
184+
.build();
185+
186+
assert_that(cargo_process().arg("install").arg("bar"),
187+
execs().with_status(101).with_stderr(
188+
" Installing bar v0.1.0
189+
error: failed to compile `bar v0.1.0`, intermediate artifacts can be found at `[..]`
190+
191+
Caused by:
192+
no matching package named `baz` found (required by `bar`)
193+
location searched: registry https://github.com/rust-lang/crates.io-index
194+
version required: ^9.8.7
195+
"));
196+
}
197+
198+
#[test]
199+
fn install_without_feature_dep() {
200+
setup();
201+
202+
VendorPackage::new("foo")
203+
.file("Cargo.toml", r#"
204+
[package]
205+
name = "foo"
206+
version = "0.1.0"
207+
authors = []
208+
"#)
209+
.file("src/lib.rs", "pub fn foo() {}")
210+
.build();
211+
212+
VendorPackage::new("bar")
213+
.file("Cargo.toml", r#"
214+
[package]
215+
name = "bar"
216+
version = "0.1.0"
217+
authors = []
218+
219+
[dependencies]
220+
foo = "0.1.0"
221+
baz = { version = "9.8.7", optional = true }
222+
223+
[features]
224+
wantbaz = ["baz"]
225+
"#)
226+
.file("src/main.rs", r#"
227+
extern crate foo;
228+
229+
pub fn main() {
230+
foo::foo();
231+
}
232+
"#)
233+
.build();
234+
235+
assert_that(cargo_process().arg("install").arg("bar"),
236+
execs().with_status(0).with_stderr(
237+
" Installing bar v0.1.0
238+
Compiling foo v0.1.0
239+
Compiling bar v0.1.0
240+
Finished release [optimized] target(s) in [..] secs
241+
Installing [..]bar[..]
242+
warning: be sure to add `[..]` to your PATH to be able to run the installed binaries
243+
"));
244+
}
245+
107246
#[test]
108247
fn not_there() {
109248
setup();

0 commit comments

Comments
 (0)