Add resolver opt-in for new feature resolver.

This commit is contained in:
Eric Huss 2020-04-17 11:20:38 -07:00
parent f044cf9fb0
commit 787e75b797
15 changed files with 668 additions and 76 deletions

View File

@ -473,6 +473,27 @@ impl Package {
}
fn make_archive(&self) {
let dst = self.archive_dst();
t!(fs::create_dir_all(dst.parent().unwrap()));
let f = t!(File::create(&dst));
let mut a = Builder::new(GzEncoder::new(f, Compression::default()));
if !self.files.iter().any(|(name, _)| name == "Cargo.toml") {
self.append_manifest(&mut a);
}
if self.files.is_empty() {
self.append(&mut a, "src/lib.rs", "");
} else {
for &(ref name, ref contents) in self.files.iter() {
self.append(&mut a, name, contents);
}
}
for &(ref name, ref contents) in self.extra_files.iter() {
self.append_extra(&mut a, name, contents);
}
}
fn append_manifest<W: Write>(&self, ar: &mut Builder<W>) {
let mut manifest = format!(
r#"
[package]
@ -508,21 +529,7 @@ impl Package {
manifest.push_str("[lib]\nproc-macro = true\n");
}
let dst = self.archive_dst();
t!(fs::create_dir_all(dst.parent().unwrap()));
let f = t!(File::create(&dst));
let mut a = Builder::new(GzEncoder::new(f, Compression::default()));
self.append(&mut a, "Cargo.toml", &manifest);
if self.files.is_empty() {
self.append(&mut a, "src/lib.rs", "");
} else {
for &(ref name, ref contents) in self.files.iter() {
self.append(&mut a, name, contents);
}
}
for &(ref name, ref contents) in self.extra_files.iter() {
self.append_extra(&mut a, name, contents);
}
self.append(ar, "Cargo.toml", &manifest);
}
fn append<W: Write>(&self, ar: &mut Builder<W>, file: &str, contents: &str) {

View File

@ -71,6 +71,7 @@ pub fn resolve_std<'cfg>(
ws_config,
/*profiles*/ None,
crate::core::Features::default(),
None,
);
let config = ws.config();

View File

@ -211,6 +211,9 @@ features! {
// Allow to specify profiles other than 'dev', 'release', 'test', etc.
[unstable] named_profiles: bool,
// Opt-in new-resolver behavior.
[unstable] resolver: bool,
}
}

View File

@ -10,6 +10,7 @@ use serde::Serialize;
use url::Url;
use crate::core::interning::InternedString;
use crate::core::resolver::ResolveBehavior;
use crate::core::{Dependency, PackageId, PackageIdSpec, SourceId, Summary};
use crate::core::{Edition, Feature, Features, WorkspaceConfig};
use crate::util::errors::*;
@ -44,6 +45,7 @@ pub struct Manifest {
im_a_teapot: Option<bool>,
default_run: Option<String>,
metabuild: Option<Vec<String>>,
resolve_behavior: Option<ResolveBehavior>,
}
/// When parsing `Cargo.toml`, some warnings should silenced
@ -66,6 +68,7 @@ pub struct VirtualManifest {
profiles: Option<TomlProfiles>,
warnings: Warnings,
features: Features,
resolve_behavior: Option<ResolveBehavior>,
}
/// General metadata about a package which is just blindly uploaded to the
@ -410,6 +413,7 @@ impl Manifest {
default_run: Option<String>,
original: Rc<TomlManifest>,
metabuild: Option<Vec<String>>,
resolve_behavior: Option<ResolveBehavior>,
) -> Manifest {
Manifest {
summary,
@ -432,6 +436,7 @@ impl Manifest {
default_run,
publish_lockfile,
metabuild,
resolve_behavior,
}
}
@ -501,6 +506,13 @@ impl Manifest {
&self.features
}
/// The style of resolver behavior to use, declared with the `resolver` field.
///
/// Returns `None` if it is not specified.
pub fn resolve_behavior(&self) -> Option<ResolveBehavior> {
self.resolve_behavior
}
pub fn map_source(self, to_replace: SourceId, replace_with: SourceId) -> Manifest {
Manifest {
summary: self.summary.map_source(to_replace, replace_with),
@ -564,6 +576,7 @@ impl VirtualManifest {
workspace: WorkspaceConfig,
profiles: Option<TomlProfiles>,
features: Features,
resolve_behavior: Option<ResolveBehavior>,
) -> VirtualManifest {
VirtualManifest {
replace,
@ -572,6 +585,7 @@ impl VirtualManifest {
profiles,
warnings: Warnings::new(),
features,
resolve_behavior,
}
}
@ -602,6 +616,13 @@ impl VirtualManifest {
pub fn features(&self) -> &Features {
&self.features
}
/// The style of resolver behavior to use, declared with the `resolver` field.
///
/// Returns `None` if it is not specified.
pub fn resolve_behavior(&self) -> Option<ResolveBehavior> {
self.resolve_behavior
}
}
impl Target {

View File

@ -23,13 +23,27 @@ use crate::core::interning::InternedString;
use crate::core::resolver::{HasDevUnits, Resolve};
use crate::core::source::MaybePackage;
use crate::core::{Dependency, Manifest, PackageId, SourceId, Target};
use crate::core::{FeatureMap, SourceMap, Summary};
use crate::core::{FeatureMap, SourceMap, Summary, Workspace};
use crate::ops;
use crate::util::config::PackageCacheLock;
use crate::util::errors::{CargoResult, CargoResultExt, HttpNot200};
use crate::util::network::Retry;
use crate::util::{self, internal, Config, Progress, ProgressStyle};
pub const MANIFEST_PREAMBLE: &str = "\
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# \"normalize\" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies
#
# If you believe there's an error in this file please file an
# issue against the rust-lang/cargo repository. If you're
# editing this file be aware that the upstream Cargo.toml
# will likely look very different (and much more reasonable)
";
/// Information about a package that is available somewhere in the file system.
///
/// A package is a `Cargo.toml` file plus all the files that are part of it.
@ -209,29 +223,13 @@ impl Package {
}
}
pub fn to_registry_toml(&self, config: &Config) -> CargoResult<String> {
pub fn to_registry_toml(&self, ws: &Workspace<'_>) -> CargoResult<String> {
let manifest = self
.manifest()
.original()
.prepare_for_publish(config, self.root())?;
.prepare_for_publish(ws, self.root())?;
let toml = toml::to_string(&manifest)?;
Ok(format!(
"# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO\n\
#\n\
# When uploading crates to the registry Cargo will automatically\n\
# \"normalize\" Cargo.toml files for maximal compatibility\n\
# with all versions of Cargo and also rewrite `path` dependencies\n\
# to registry (e.g., crates.io) dependencies\n\
#\n\
# If you believe there's an error in this file please file an\n\
# issue against the rust-lang/cargo repository. If you're\n\
# editing this file be aware that the upstream Cargo.toml\n\
# will likely look very different (and much more reasonable)\n\
\n\
{}\
",
toml
))
Ok(format!("{}\n{}", MANIFEST_PREAMBLE, toml))
}
/// Returns if package should include `Cargo.lock`.

View File

@ -41,9 +41,9 @@
use crate::core::compiler::{CompileKind, RustcTargetData};
use crate::core::dependency::{DepKind, Dependency};
use crate::core::resolver::types::FeaturesSet;
use crate::core::resolver::Resolve;
use crate::core::resolver::{Resolve, ResolveBehavior};
use crate::core::{FeatureValue, InternedString, PackageId, PackageIdSpec, PackageSet, Workspace};
use crate::util::{CargoResult, Config};
use crate::util::CargoResult;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::rc::Rc;
@ -110,9 +110,9 @@ impl FeaturesFor {
}
impl FeatureOpts {
fn new(config: &Config, has_dev_units: HasDevUnits) -> CargoResult<FeatureOpts> {
fn new(ws: &Workspace<'_>, has_dev_units: HasDevUnits) -> CargoResult<FeatureOpts> {
let mut opts = FeatureOpts::default();
let unstable_flags = config.cli_unstable();
let unstable_flags = ws.config().cli_unstable();
opts.package_features = unstable_flags.package_features;
let mut enable = |feat_opts: &Vec<String>| {
opts.new_resolver = true;
@ -136,6 +136,12 @@ impl FeatureOpts {
if let Some(feat_opts) = unstable_flags.features.as_ref() {
enable(feat_opts)?;
}
match ws.resolve_behavior() {
ResolveBehavior::V1 => {}
ResolveBehavior::V2 => {
enable(&vec!["all".to_string()]).unwrap();
}
}
// This env var is intended for testing only.
if let Ok(env_opts) = std::env::var("__CARGO_FORCE_NEW_FEATURES") {
if env_opts == "1" {
@ -146,6 +152,7 @@ impl FeatureOpts {
}
}
if let HasDevUnits::Yes = has_dev_units {
// Dev deps cannot be decoupled when they are in use.
opts.decouple_dev_deps = false;
}
Ok(opts)
@ -268,7 +275,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
use crate::util::profile;
let _p = profile::start("resolve features");
let opts = FeatureOpts::new(ws.config(), has_dev_units)?;
let opts = FeatureOpts::new(ws, has_dev_units)?;
if !opts.new_resolver {
// Legacy mode.
return Ok(ResolvedFeatures {

View File

@ -71,7 +71,7 @@ pub use self::encode::{EncodableDependency, EncodablePackageId, EncodableResolve
pub use self::errors::{ActivateError, ActivateResult, ResolveError};
pub use self::features::HasDevUnits;
pub use self::resolve::{Resolve, ResolveVersion};
pub use self::types::ResolveOpts;
pub use self::types::{ResolveBehavior, ResolveOpts};
mod conflict_cache;
mod context;

View File

@ -97,6 +97,35 @@ impl ResolverProgress {
/// optimized comparison operators like `is_subset` at the interfaces.
pub type FeaturesSet = Rc<BTreeSet<InternedString>>;
/// Resolver behavior, used to opt-in to new behavior that is
/// backwards-incompatible via the `resolver` field in the manifest.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum ResolveBehavior {
/// V1 is the original resolver behavior.
V1,
/// V2 adds the new feature resolver.
V2,
}
impl ResolveBehavior {
pub fn from_manifest(resolver: &str) -> CargoResult<ResolveBehavior> {
match resolver {
"2" => Ok(ResolveBehavior::V2),
s => anyhow::bail!(
"`resolver` setting `{}` is not valid, only valid option is \"2\"",
s
),
}
}
pub fn to_manifest(&self) -> Option<String> {
match self {
ResolveBehavior::V1 => None,
ResolveBehavior::V2 => Some("2".to_string()),
}
}
}
/// Options for how the resolve should work.
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct ResolveOpts {

View File

@ -12,6 +12,7 @@ use url::Url;
use crate::core::features::Features;
use crate::core::registry::PackageRegistry;
use crate::core::resolver::features::RequestedFeatures;
use crate::core::resolver::ResolveBehavior;
use crate::core::{Dependency, InternedString, PackageId, PackageIdSpec};
use crate::core::{EitherManifest, Package, SourceId, VirtualManifest};
use crate::ops;
@ -84,6 +85,9 @@ pub struct Workspace<'cfg> {
// If `true`, then the resolver will ignore any existing `Cargo.lock`
// file. This is set for `cargo install` without `--locked`.
ignore_lock: bool,
/// The resolver behavior specified with the `resolver` field.
resolve_behavior: ResolveBehavior,
}
// Separate structure for tracking loaded packages (to avoid loading anything
@ -143,6 +147,11 @@ impl<'cfg> Workspace<'cfg> {
ws.target_dir = config.target_dir()?;
ws.root_manifest = ws.find_root(manifest_path)?;
ws.find_members()?;
ws.resolve_behavior = match ws.root_maybe() {
MaybePackage::Package(p) => p.manifest().resolve_behavior(),
MaybePackage::Virtual(vm) => vm.resolve_behavior(),
}
.unwrap_or(ResolveBehavior::V1);
ws.validate()?;
Ok(ws)
}
@ -164,6 +173,7 @@ impl<'cfg> Workspace<'cfg> {
require_optional_deps: true,
loaded_packages: RefCell::new(HashMap::new()),
ignore_lock: false,
resolve_behavior: ResolveBehavior::V1,
}
}
@ -176,6 +186,7 @@ impl<'cfg> Workspace<'cfg> {
let mut ws = Workspace::new_default(current_manifest, config);
ws.root_manifest = Some(root_path.join("Cargo.toml"));
ws.target_dir = config.target_dir()?;
ws.resolve_behavior = manifest.resolve_behavior().unwrap_or(ResolveBehavior::V1);
ws.packages
.packages
.insert(root_path, MaybePackage::Virtual(manifest));
@ -203,6 +214,10 @@ impl<'cfg> Workspace<'cfg> {
let mut ws = Workspace::new_default(package.manifest_path().to_path_buf(), config);
ws.is_ephemeral = true;
ws.require_optional_deps = require_optional_deps;
ws.resolve_behavior = package
.manifest()
.resolve_behavior()
.unwrap_or(ResolveBehavior::V1);
let key = ws.current_manifest.parent().unwrap();
let id = package.package_id();
let package = MaybePackage::Package(package);
@ -578,6 +593,18 @@ impl<'cfg> Workspace<'cfg> {
}
}
pub fn resolve_behavior(&self) -> ResolveBehavior {
self.resolve_behavior
}
pub fn allows_unstable_package_features(&self) -> bool {
self.config().cli_unstable().package_features
|| match self.resolve_behavior() {
ResolveBehavior::V1 => false,
ResolveBehavior::V2 => true,
}
}
/// Validates a workspace, ensuring that a number of invariants are upheld:
///
/// 1. A workspace only has one root.
@ -769,6 +796,12 @@ impl<'cfg> Workspace<'cfg> {
if !manifest.patch().is_empty() {
emit_warning("patch")?;
}
if let Some(behavior) = manifest.resolve_behavior() {
// Only warn if they don't match.
if behavior != self.resolve_behavior {
emit_warning("resolver")?;
}
}
}
}
Ok(())
@ -878,7 +911,7 @@ impl<'cfg> Workspace<'cfg> {
.map(|m| (m, RequestedFeatures::new_all(true)))
.collect());
}
if self.config().cli_unstable().package_features {
if self.allows_unstable_package_features() {
self.members_with_features_pf(specs, requested_features)
} else {
self.members_with_features_stable(specs, requested_features)

View File

@ -156,7 +156,7 @@ fn build_ar_list(
rel_str: "Cargo.toml.orig".to_string(),
contents: FileContents::OnDisk(src_file),
});
let generated = pkg.to_registry_toml(ws.config())?;
let generated = pkg.to_registry_toml(ws)?;
result.push(ArchiveFile {
rel_path,
rel_str,
@ -267,7 +267,7 @@ fn build_lock(ws: &Workspace<'_>) -> CargoResult<String> {
orig_pkg
.manifest()
.original()
.prepare_for_publish(config, orig_pkg.root())?,
.prepare_for_publish(ws, orig_pkg.root())?,
);
let package_root = orig_pkg.root();
let source_id = orig_pkg.package_id().source_id();

View File

@ -301,7 +301,7 @@ pub trait ArgMatchesExt {
if config.cli_unstable().avoid_dev_deps {
ws.set_require_optional_deps(false);
}
if ws.is_virtual() && !config.cli_unstable().package_features {
if ws.is_virtual() && !ws.allows_unstable_package_features() {
// --all-features is actually honored. In general, workspaces and
// feature flags are a bit of a mess right now.
for flag in &["features", "no-default-features"] {

View File

@ -15,8 +15,9 @@ use url::Url;
use crate::core::dependency::DepKind;
use crate::core::manifest::{LibKind, ManifestMetadata, TargetSourcePath, Warnings};
use crate::core::resolver::ResolveBehavior;
use crate::core::{Dependency, InternedString, Manifest, PackageId, Summary, Target};
use crate::core::{Edition, EitherManifest, Feature, Features, VirtualManifest};
use crate::core::{Edition, EitherManifest, Feature, Features, VirtualManifest, Workspace};
use crate::core::{GitReference, PackageIdSpec, SourceId, WorkspaceConfig, WorkspaceRootConfig};
use crate::sources::{CRATES_IO_INDEX, CRATES_IO_REGISTRY};
use crate::util::errors::{CargoResult, CargoResultExt, ManifestError};
@ -805,6 +806,7 @@ pub struct TomlProject {
license_file: Option<String>,
repository: Option<String>,
metadata: Option<toml::Value>,
resolver: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
@ -813,6 +815,7 @@ pub struct TomlWorkspace {
#[serde(rename = "default-members")]
default_members: Option<Vec<String>>,
exclude: Option<Vec<String>>,
resolver: Option<String>,
}
impl TomlProject {
@ -836,9 +839,10 @@ struct Context<'a, 'b> {
impl TomlManifest {
pub fn prepare_for_publish(
&self,
config: &Config,
ws: &Workspace<'_>,
package_root: &Path,
) -> CargoResult<TomlManifest> {
let config = ws.config();
let mut package = self
.package
.as_ref()
@ -846,6 +850,19 @@ impl TomlManifest {
.unwrap()
.clone();
package.workspace = None;
let mut cargo_features = self.cargo_features.clone();
package.resolver = ws.resolve_behavior().to_manifest();
if package.resolver.is_some() {
// This should be removed when stabilizing.
match &mut cargo_features {
None => cargo_features = Some(vec!["resolver".to_string()]),
Some(feats) => {
if !feats.iter().any(|feat| feat == "resolver") {
feats.push("resolver".to_string());
}
}
}
}
if let Some(license_file) = &package.license_file {
let license_path = Path::new(&license_file);
let abs_license_path = paths::normalize_path(&package_root.join(license_path));
@ -927,7 +944,7 @@ impl TomlManifest {
patch: None,
workspace: None,
badges: self.badges.clone(),
cargo_features: self.cargo_features.clone(),
cargo_features,
});
fn map_deps(
@ -1015,6 +1032,25 @@ impl TomlManifest {
features.require(Feature::metabuild())?;
}
if project.resolver.is_some()
|| me
.workspace
.as_ref()
.map_or(false, |ws| ws.resolver.is_some())
{
features.require(Feature::resolver())?;
}
let resolve_behavior = match (
project.resolver.as_ref(),
me.workspace.as_ref().and_then(|ws| ws.resolver.as_ref()),
) {
(None, None) => None,
(Some(s), None) | (None, Some(s)) => Some(ResolveBehavior::from_manifest(s)?),
(Some(_), Some(_)) => {
bail!("cannot specify `resolver` field in both `[workspace]` and `[package]`")
}
};
// If we have no lib at all, use the inferred lib, if available.
// If we have a lib with a path, we're done.
// If we have a lib with no path, use the inferred lib or else the package name.
@ -1256,6 +1292,7 @@ impl TomlManifest {
project.default_run.clone(),
Rc::clone(me),
project.metabuild.clone().map(|sov| sov.0),
resolve_behavior,
);
if project.license_file.is_some() && project.license.is_some() {
manifest.warnings_mut().add_warning(
@ -1347,6 +1384,19 @@ impl TomlManifest {
if let Some(profiles) = &profiles {
profiles.validate(&features, &mut warnings)?;
}
if me
.workspace
.as_ref()
.map_or(false, |ws| ws.resolver.is_some())
{
features.require(Feature::resolver())?;
}
let resolve_behavior = me
.workspace
.as_ref()
.and_then(|ws| ws.resolver.as_deref())
.map(|r| ResolveBehavior::from_manifest(r))
.transpose()?;
let workspace_config = match me.workspace {
Some(ref config) => WorkspaceConfig::Root(WorkspaceRootConfig::new(
root,
@ -1359,7 +1409,14 @@ impl TomlManifest {
}
};
Ok((
VirtualManifest::new(replace, patch, workspace_config, profiles, features),
VirtualManifest::new(
replace,
patch,
workspace_config,
profiles,
features,
resolve_behavior,
),
nested_paths,
))
}

View File

@ -574,6 +574,40 @@ make feature flags behave in a more intuitive manner.
The ability to set features for non-workspace members is no longer allowed, as
the resolver fundamentally does not support that ability.
### Resolver
* Tracking Issue: [#8088](https://github.com/rust-lang/cargo/issues/8088)
The `resolver` feature allows the resolver version to be specified in the
`Cargo.toml` manifest. This allows a project to opt-in to
backwards-incompatible changes in the resolver.
```toml
cargo-features = ["resolver"]
[package]
name = "my-package"
version = "1.0.0"
resolver = "2"
```
Currently the only allowed value is `"2"`. This declaration enables all of the
new feature behavior of [`-Zfeatures=all`](#features) and
[`-Zpackage-features`](#package-features).
This flag is global for a workspace. If using a virtual workspace, the root
definition should be in the `[workspace]` table like this:
```toml
cargo-features = ["resolver"]
[workspace]
members = ["member1", "member2"]
resolver = "2"
```
The `resolver` field is ignored in dependencies, only the top-level project or
workspace can control the new behavior.
### crate-versions
* Tracking Issue: [#7907](https://github.com/rust-lang/cargo/issues/7907)

View File

@ -2,8 +2,10 @@
use cargo_test_support::cross_compile::{self, alternate};
use cargo_test_support::paths::CargoPathExt;
use cargo_test_support::publish::validate_crate_contents;
use cargo_test_support::registry::{Dependency, Package};
use cargo_test_support::{basic_manifest, project, rustc_host};
use cargo_test_support::{basic_manifest, cargo_process, project, rustc_host};
use std::fs::File;
#[cargo_test]
fn inactivate_targets() {
@ -1289,3 +1291,421 @@ fn build_dep_activated() {
.masquerade_as_nightly_cargo()
.run();
}
#[cargo_test]
fn resolver_gated() {
// Check that `resolver` field is feature gated.
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
resolver = "2"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("build")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
error: failed to parse manifest at `[..]/foo/Cargo.toml`
Caused by:
feature `resolver` is required
consider adding `cargo-features = [\"resolver\"]` to the manifest
",
)
.run();
// Test with virtual ws.
let p = project()
.file(
"Cargo.toml",
r#"
[workspace]
members = ["a"]
resolver = "2"
"#,
)
.file("a/Cargo.toml", &basic_manifest("a", "0.1.0"))
.file("a/src/lib.rs", "")
.build();
p.cargo("build")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
error: failed to parse manifest at `[..]/foo/Cargo.toml`
Caused by:
feature `resolver` is required
consider adding `cargo-features = [\"resolver\"]` to the manifest
",
)
.run();
}
#[cargo_test]
fn resolver_bad_setting() {
// Unknown setting in `resolver`
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["resolver"]
[package]
name = "foo"
version = "0.1.0"
resolver = "1"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("build")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
error: failed to parse manifest at `[..]/foo/Cargo.toml`
Caused by:
`resolver` setting `1` is not valid, only valid option is \"2\"
",
)
.run();
}
#[cargo_test]
fn resolver_not_both() {
// Can't specify resolver in both workspace and package.
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["resolver"]
[workspace]
resolver = "2"
[package]
name = "foo"
version = "0.1.0"
resolver = "2"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("build")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
error: failed to parse manifest at `[..]/foo/Cargo.toml`
Caused by:
cannot specify `resolver` field in both `[workspace]` and `[package]`
",
)
.run();
}
#[cargo_test]
fn resolver_ws_member() {
// Can't specify `resolver` in a ws member.
let p = project()
.file(
"Cargo.toml",
r#"
[workspace]
members = ["a"]
"#,
)
.file(
"a/Cargo.toml",
r#"
cargo-features = ["resolver"]
[package]
name = "a"
version = "0.1.0"
resolver = "2"
"#,
)
.file("a/src/lib.rs", "")
.build();
p.cargo("check")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
warning: resolver for the non root package will be ignored, specify resolver at the workspace root:
package: [..]/foo/a/Cargo.toml
workspace: [..]/foo/Cargo.toml
[CHECKING] a v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn resolver_ws_root_and_member() {
// Check when specified in both ws root and member.
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["resolver"]
[workspace]
members = ["a"]
resolver = "2"
"#,
)
.file(
"a/Cargo.toml",
r#"
cargo-features = ["resolver"]
[package]
name = "a"
version = "0.1.0"
resolver = "2"
"#,
)
.file("a/src/lib.rs", "")
.build();
// Ignores if they are the same.
p.cargo("check")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[CHECKING] a v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
}
#[cargo_test]
fn resolver_enables_new_features() {
// resolver="2" enables all the things.
Package::new("common", "1.0.0")
.feature("normal", &[])
.feature("build", &[])
.feature("dev", &[])
.feature("itarget", &[])
.file(
"src/lib.rs",
r#"
pub fn feats() -> u32 {
let mut res = 0;
if cfg!(feature="normal") { res |= 1; }
if cfg!(feature="build") { res |= 2; }
if cfg!(feature="dev") { res |= 4; }
if cfg!(feature="itarget") { res |= 8; }
res
}
"#,
)
.publish();
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["resolver"]
[workspace]
members = ["a", "b"]
resolver = "2"
"#,
)
.file(
"a/Cargo.toml",
r#"
[package]
name = "a"
version = "0.1.0"
edition = "2018"
[dependencies]
common = {version = "1.0", features=["normal"]}
[dev-dependencies]
common = {version = "1.0", features=["dev"]}
[build-dependencies]
common = {version = "1.0", features=["build"]}
[target.'cfg(whatever)'.dependencies]
common = {version = "1.0", features=["itarget"]}
"#,
)
.file(
"a/src/main.rs",
r#"
fn main() {
expect();
}
fn expect() {
let expected: u32 = std::env::var("EXPECTED_FEATS").unwrap().parse().unwrap();
assert_eq!(expected, common::feats());
}
#[test]
fn from_test() {
expect();
}
"#,
)
.file(
"b/Cargo.toml",
r#"
[package]
name = "b"
version = "0.1.0"
[features]
ping = []
"#,
)
.file(
"b/src/main.rs",
r#"
fn main() {
if cfg!(feature="ping") {
println!("pong");
}
}
"#,
)
.build();
// Only normal.
p.cargo("run --bin a")
.masquerade_as_nightly_cargo()
.env("EXPECTED_FEATS", "1")
.run();
// only normal+dev
p.cargo("test")
.cwd("a")
.masquerade_as_nightly_cargo()
.env("EXPECTED_FEATS", "5")
.run();
// -Zpackage-features is enabled.
p.cargo("run -p b --features=ping")
.cwd("a")
.masquerade_as_nightly_cargo()
.with_stdout("pong")
.run();
}
#[cargo_test]
fn install_resolve_behavior() {
// install honors the resolver behavior.
Package::new("common", "1.0.0")
.feature("f1", &[])
.file(
"src/lib.rs",
r#"
#[cfg(feature = "f1")]
compile_error!("f1 should not activate");
"#,
)
.publish();
Package::new("bar", "1.0.0").dep("common", "1.0").publish();
Package::new("foo", "1.0.0")
.file(
"Cargo.toml",
r#"
cargo-features = ["resolver"]
[package]
name = "foo"
version = "1.0.0"
resolver = "2"
[target.'cfg(whatever)'.dependencies]
common = {version="1.0", features=["f1"]}
[dependencies]
bar = "1.0"
"#,
)
.file("src/main.rs", "fn main() {}")
.publish();
cargo_process("install foo")
.masquerade_as_nightly_cargo()
.run();
}
#[cargo_test]
fn package_includes_resolve_behavior() {
// `cargo package` will inherit the correct resolve behavior.
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["resolver"]
[workspace]
members = ["a"]
resolver = "2"
"#,
)
.file(
"a/Cargo.toml",
r#"
[package]
name = "a"
version = "0.1.0"
authors = ["Zzz"]
description = "foo"
license = "MIT"
homepage = "https://example.com/"
"#,
)
.file("a/src/lib.rs", "")
.build();
p.cargo("package")
.cwd("a")
.masquerade_as_nightly_cargo()
.run();
let rewritten_toml = format!(
r#"{}
cargo-features = ["resolver"]
[package]
name = "a"
version = "0.1.0"
authors = ["Zzz"]
description = "foo"
homepage = "https://example.com/"
license = "MIT"
resolver = "2"
"#,
cargo::core::package::MANIFEST_PREAMBLE
);
let f = File::open(&p.root().join("target/package/a-0.1.0.crate")).unwrap();
validate_crate_contents(
f,
"a-0.1.0.crate",
&["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
&[("Cargo.toml", &rewritten_toml)],
);
}

View File

@ -854,18 +854,7 @@ fn generated_manifest() {
let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
let rewritten_toml = format!(
r#"# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies
#
# If you believe there's an error in this file please file an
# issue against the rust-lang/cargo repository. If you're
# editing this file be aware that the upstream Cargo.toml
# will likely look very different (and much more reasonable)
r#"{}
[package]
name = "foo"
version = "0.0.1"
@ -889,6 +878,7 @@ registry-index = "{}"
[dependencies.ghi]
version = "1.0"
"#,
cargo::core::package::MANIFEST_PREAMBLE,
registry::alt_registry_url()
);
@ -935,28 +925,20 @@ fn ignore_workspace_specifier() {
p.cargo("package --no-verify").cwd("bar").run();
let f = File::open(&p.root().join("target/package/bar-0.1.0.crate")).unwrap();
let rewritten_toml = r#"# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies
#
# If you believe there's an error in this file please file an
# issue against the rust-lang/cargo repository. If you're
# editing this file be aware that the upstream Cargo.toml
# will likely look very different (and much more reasonable)
let rewritten_toml = format!(
r#"{}
[package]
name = "bar"
version = "0.1.0"
authors = []
"#;
"#,
cargo::core::package::MANIFEST_PREAMBLE
);
validate_crate_contents(
f,
"bar-0.1.0.crate",
&["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
&[("Cargo.toml", rewritten_toml)],
&[("Cargo.toml", &rewritten_toml)],
);
}