Skip to content
7 changes: 6 additions & 1 deletion src/bin/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub struct Options {
flag_features: Vec<String>,
flag_all_features: bool,
flag_no_default_features: bool,
flag_avoid_dev_deps: bool,
flag_target: Option<String>,
flag_manifest_path: Option<String>,
flag_verbose: u32,
Expand Down Expand Up @@ -63,6 +64,7 @@ Options:
--features FEATURES Space-separated list of features to also build
--all-features Build all available features
--no-default-features Do not build the `default` feature
--avoid-dev-deps Avoid installing dev-dependencies if possible
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this start out as a -Z flag to be unstable by default?

--target TRIPLE Build for the target triple
--manifest-path PATH Path to the manifest to compile
-v, --verbose ... Use verbose output (-vv very verbose/build.rs output)
Expand Down Expand Up @@ -98,7 +100,10 @@ pub fn execute(options: Options, config: &mut Config) -> CliResult {
&options.flag_z)?;

let root = find_root_manifest_for_wd(options.flag_manifest_path, config.cwd())?;
let ws = Workspace::new(&root, config)?;
let mut ws = Workspace::new(&root, config)?;
if options.flag_avoid_dev_deps {
ws.set_require_optional_deps(false);
}

let spec = Packages::from_flags(options.flag_all,
&options.flag_exclude,
Expand Down
21 changes: 18 additions & 3 deletions src/cargo/core/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,26 @@ pub struct DepsNotReplaced<'a> {

#[derive(Clone, Copy)]
pub enum Method<'a> {
Everything,
Everything, // equivalent to Required { dev_deps: true, all_features: true, .. }
Required {
dev_deps: bool,
features: &'a [String],
all_features: bool,
uses_default_features: bool,
},
}

impl<'r> Method<'r> {
pub fn split_features(features: &[String]) -> Vec<String> {
features.iter()
.flat_map(|s| s.split_whitespace())
.flat_map(|s| s.split(','))
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
.collect::<Vec<String>>()
}
}

// Information about the dependencies for a crate, a tuple of:
//
// (dependency info, candidates, features activated)
Expand Down Expand Up @@ -731,6 +743,7 @@ fn activate_deps_loop<'a>(mut cx: Context<'a>,
let method = Method::Required {
dev_deps: false,
features: &features,
all_features: false,
uses_default_features: dep.uses_default_features(),
};
trace!("{}[{}]>{} trying {}", parent.name(), cur, dep.name(),
Expand Down Expand Up @@ -1006,7 +1019,8 @@ fn build_requirements<'a, 'b: 'a>(s: &'a Summary, method: &'b Method)
-> CargoResult<Requirements<'a>> {
let mut reqs = Requirements::new(s);
match *method {
Method::Everything => {
Method::Everything |
Method::Required { all_features: true, .. } => {
for key in s.features().keys() {
reqs.require_feature(key)?;
}
Expand Down Expand Up @@ -1052,10 +1066,11 @@ impl<'a> Context<'a> {
}
debug!("checking if {} is already activated", summary.package_id());
let (features, use_default) = match *method {
Method::Everything |
Method::Required { all_features: true, .. } => return false,
Method::Required { features, uses_default_features, .. } => {
(features, uses_default_features)
}
Method::Everything => return false,
};

let has_default_feature = summary.features().contains_key("default");
Expand Down
5 changes: 5 additions & 0 deletions src/cargo/core/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,11 @@ impl<'cfg> Workspace<'cfg> {
self.require_optional_deps
}

pub fn set_require_optional_deps<'a>(&'a mut self, require_optional_deps: bool) -> &mut Workspace<'cfg> {
self.require_optional_deps = require_optional_deps;
self
}

/// Finds the root of a workspace for the crate whose manifest is located
/// at `manifest_path`.
///
Expand Down
34 changes: 26 additions & 8 deletions src/cargo/ops/cargo_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use std::sync::Arc;

use core::{Source, Package, Target};
use core::{Profile, TargetKind, Profiles, Workspace, PackageId, PackageIdSpec};
use core::resolver::Resolve;
use core::resolver::{Resolve, Method};
use ops::{self, BuildOutput, Executor, DefaultExecutor};
use util::config::Config;
use util::{CargoResult, profile};
Expand Down Expand Up @@ -226,12 +226,18 @@ pub fn compile_ws<'a>(ws: &Workspace<'a>,
let profiles = ws.profiles();

let specs = spec.into_package_id_specs(ws)?;
let resolve = ops::resolve_ws_precisely(ws,
source,
features,
all_features,
no_default_features,
&specs)?;
let features = Method::split_features(features);
let method = Method::Required {
dev_deps: ws.require_optional_deps() || filter.need_dev_deps(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps the name optional_deps could change since it's affecting dev-dependencies?

Additionally, I think that we'd want this flag to affect target-specific dependencies, right? If this is a sort of "one shot" compilation there should also be no need to resolve and require windows-specific dependencies when on Linux, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind if we leave that as a TODO for the future? What you say makes sense but it is not actually needed in Debian right now, because we union over all targets when translating dependencies - so e.g. winapi etc are all pulled in regardless of what target you're building for. This means we have to have less special-cases in the packaging, and also supports cross-compilation later.

If I understood correctly, on Fedora they are patching out windows dependencies so they don't appear in Cargo.toml, so this functionality wouldn't be needed either. (Perhaps @ignatenkobrain can confirm).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok sure yeah, I think a TODO should suffice!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure if it's appropriate to mention "targets" in the sense of "platform" here on this line. The term "target" in the context of CompileFilter and core::Target and cargo build options, seems rather to mean "lib/bin/examples/tests" etc and not platform.

OTOH the functionality you mention does seem to be missing - if I add [target."XXXX".dependencies] YYY = "999" to Cargo.toml then cargo install still fails with this PR, despite the fact that "XXXX" does not match the current platform. I'm just unsure of the appropriate place to add this TODO, please advise. Perhaps on struct Method?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm ok, perhaps an issue can be filed? I think the -Z avoid-dev-deps flag added here may in the long run want to be something like --minimal-cargo-lock which prunes all non-relevant dependencies like platform-specific dependencies that don't apply, dev-deps if you're not building tests, etc. In that sense I think the feature/TODO here is slightly broader than a comment in the code. Would you be ok filing that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed as #5133

features: &features,
all_features,
uses_default_features: !no_default_features,
};
let resolve = ops::resolve_ws_with_method(ws,
source,
method,
&specs,
)?;
let (packages, resolve_with_overrides) = resolve;

if specs.is_empty() {
Expand Down Expand Up @@ -413,9 +419,21 @@ impl<'a> CompileFilter<'a> {
}
}

pub fn need_dev_deps(&self) -> bool {
match *self {
CompileFilter::Default { .. } => false,
CompileFilter::Only { examples, tests, benches, .. } =>
examples.is_specific() || tests.is_specific() || benches.is_specific()
}
}

pub fn matches(&self, target: &Target) -> bool {
match *self {
CompileFilter::Default { .. } => true,
CompileFilter::Default { .. } => match *target.kind() {
TargetKind::Bin => true,
TargetKind::Lib(..) => true,
_ => false,
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come this needed changing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not necessary and could be dropped from this PR. But without it, the current situation means I have to give extra flags to cargo build, it contradicts the documentation, and it also means the all-targets flag is redundant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm sorry I missed this earlier, but I'm not sure I quite understand this. Could this perhaps be left out for a future PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(or maybe a test could be added?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually opened up a whole new can of worms, so I've reverted it from this PR and opened up a new issue #5134 which I'll file a PR for as soon as this PR is merged (I have a fix, but it builds on top of this PR).

CompileFilter::Only { lib, bins, examples, tests, benches, .. } => {
let rule = match *target.kind() {
TargetKind::Bin => bins,
Expand Down
6 changes: 5 additions & 1 deletion src/cargo/ops/cargo_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,11 @@ fn install_one(root: &Filesystem,

let ws = match overidden_target_dir {
Some(dir) => Workspace::ephemeral(pkg, config, Some(dir), false)?,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the optional deps flag be set for this as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ephemeral() already sets that internally

None => Workspace::new(pkg.manifest_path(), config)?,
None => {
let mut ws = Workspace::new(pkg.manifest_path(), config)?;
ws.set_require_optional_deps(false);
ws
}
};
let pkg = ws.current()?;

Expand Down
2 changes: 1 addition & 1 deletion src/cargo/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub use self::registry::{modify_owners, yank, OwnersOptions, PublishOpts};
pub use self::registry::configure_http_handle;
pub use self::cargo_fetch::fetch;
pub use self::cargo_pkgid::pkgid;
pub use self::resolve::{resolve_ws, resolve_ws_precisely, resolve_with_previous};
pub use self::resolve::{resolve_ws, resolve_ws_precisely, resolve_ws_with_method, resolve_with_previous};
pub use self::cargo_output_metadata::{output_metadata, OutputMetadataOptions, ExportInfo};

mod cargo_clean;
Expand Down
35 changes: 19 additions & 16 deletions src/cargo/ops/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,25 @@ pub fn resolve_ws_precisely<'a>(ws: &Workspace<'a>,
no_default_features: bool,
specs: &[PackageIdSpec])
-> CargoResult<(PackageSet<'a>, Resolve)> {
let features = features.iter()
.flat_map(|s| s.split_whitespace())
.flat_map(|s| s.split(','))
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
.collect::<Vec<String>>();
let features = Method::split_features(features);
let method = if all_features {
Method::Everything
} else {
Method::Required {
dev_deps: true,
features: &features,
all_features: false,
uses_default_features: !no_default_features,
}
};
resolve_ws_with_method(ws, source, method, specs)
}

pub fn resolve_ws_with_method<'a>(ws: &Workspace<'a>,
source: Option<Box<Source + 'a>>,
method: Method,
specs: &[PackageIdSpec])
-> CargoResult<(PackageSet<'a>, Resolve)> {
let mut registry = PackageRegistry::new(ws.config())?;
if let Some(source) = source {
registry.add_preloaded(source);
Expand Down Expand Up @@ -68,16 +80,6 @@ pub fn resolve_ws_precisely<'a>(ws: &Workspace<'a>,
None
};

let method = if all_features {
Method::Everything
} else {
Method::Required {
dev_deps: true, // TODO: remove this option?
features: &features,
uses_default_features: !no_default_features,
}
};

let resolved_with_overrides =
ops::resolve_with_previous(&mut registry,
ws,
Expand Down Expand Up @@ -236,6 +238,7 @@ pub fn resolve_with_previous<'a>(registry: &mut PackageRegistry,
let base = Method::Required {
dev_deps: dev_deps,
features: &[],
all_features: false,
uses_default_features: true,
};
let member_id = member.package_id();
Expand Down