Skip to content

Commit db71d87

Browse files
committed
In "cargo install" directly from registry, don't require optional dependencies
When building with a directory registry that contains only the subset of crates required to build an application crate, cargo fails if that subset doesn't include optional dependencies pulled in for every possible feature of the root crate, even when the install doesn't enable those features. This prevents Linux distributions from building with a minimal set of dependencies (omitting, for instance, packages for unstable/nightly features). Introduce a new workspace flag "require_optional_deps", disabled for install and enabled for everything else. Skip the initial Method::Everything resolve in this case, and modify resolve_with_previous to support running a Method::Required resolve without a previous resolution. This also skips adding path overrides, as those won't make sense (and won't work) for an install directly from a registry. Introduce a set of tests for "cargo install" directly from a directory registry.
1 parent bdce1f5 commit db71d87

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};
@@ -105,6 +106,144 @@ fn simple() {
105106
"));
106107
}
107108

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

0 commit comments

Comments
 (0)