diff --git a/crates/cargo-test-support/src/registry.rs b/crates/cargo-test-support/src/registry.rs index c65f9cf8a5d..cf6cff8d2c2 100644 --- a/crates/cargo-test-support/src/registry.rs +++ b/crates/cargo-test-support/src/registry.rs @@ -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(&self, ar: &mut Builder) { 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(&self, ar: &mut Builder, file: &str, contents: &str) { diff --git a/src/cargo/core/compiler/standard_lib.rs b/src/cargo/core/compiler/standard_lib.rs index 7acfcff9a1b..214ea4e739c 100644 --- a/src/cargo/core/compiler/standard_lib.rs +++ b/src/cargo/core/compiler/standard_lib.rs @@ -71,6 +71,7 @@ pub fn resolve_std<'cfg>( ws_config, /*profiles*/ None, crate::core::Features::default(), + None, ); let config = ws.config(); diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index eb2b80fbe5e..5ff87161bed 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -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, } } diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index 27f5b62fe3a..420d7194775 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -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, default_run: Option, metabuild: Option>, + resolve_behavior: Option, } /// When parsing `Cargo.toml`, some warnings should silenced @@ -66,6 +68,7 @@ pub struct VirtualManifest { profiles: Option, warnings: Warnings, features: Features, + resolve_behavior: Option, } /// General metadata about a package which is just blindly uploaded to the @@ -410,6 +413,7 @@ impl Manifest { default_run: Option, original: Rc, metabuild: Option>, + resolve_behavior: Option, ) -> 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 { + 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, features: Features, + resolve_behavior: Option, ) -> 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 { + self.resolve_behavior + } } impl Target { diff --git a/src/cargo/core/package.rs b/src/cargo/core/package.rs index 061e6c1e8ac..ced19a9293d 100644 --- a/src/cargo/core/package.rs +++ b/src/cargo/core/package.rs @@ -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 { + pub fn to_registry_toml(&self, ws: &Workspace<'_>) -> CargoResult { 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`. diff --git a/src/cargo/core/resolver/features.rs b/src/cargo/core/resolver/features.rs index bccb6f4d086..58a4005e71a 100644 --- a/src/cargo/core/resolver/features.rs +++ b/src/cargo/core/resolver/features.rs @@ -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 { + fn new(ws: &Workspace<'_>, has_dev_units: HasDevUnits) -> CargoResult { 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| { 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 { diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index 22d3f33f6a2..cde34a220b4 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -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; diff --git a/src/cargo/core/resolver/types.rs b/src/cargo/core/resolver/types.rs index 8fb56ceb422..ab988a8523e 100644 --- a/src/cargo/core/resolver/types.rs +++ b/src/cargo/core/resolver/types.rs @@ -97,6 +97,35 @@ impl ResolverProgress { /// optimized comparison operators like `is_subset` at the interfaces. pub type FeaturesSet = Rc>; +/// 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 { + 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 { + 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 { diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index 6dc6091ef33..38428229b12 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -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: Option, } // Separate structure for tracking loaded packages (to avoid loading anything @@ -143,6 +147,10 @@ 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(), + }; ws.validate()?; Ok(ws) } @@ -164,6 +172,7 @@ impl<'cfg> Workspace<'cfg> { require_optional_deps: true, loaded_packages: RefCell::new(HashMap::new()), ignore_lock: false, + resolve_behavior: None, } } @@ -176,6 +185,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(); ws.packages .packages .insert(root_path, MaybePackage::Virtual(manifest)); @@ -203,6 +213,7 @@ 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(); let key = ws.current_manifest.parent().unwrap(); let id = package.package_id(); let package = MaybePackage::Package(package); @@ -578,6 +589,18 @@ impl<'cfg> Workspace<'cfg> { } } + pub fn resolve_behavior(&self) -> ResolveBehavior { + self.resolve_behavior.unwrap_or(ResolveBehavior::V1) + } + + 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 +792,10 @@ impl<'cfg> Workspace<'cfg> { if !manifest.patch().is_empty() { emit_warning("patch")?; } + if manifest.resolve_behavior() != self.resolve_behavior { + // Only warn if they don't match. + emit_warning("resolver")?; + } } } Ok(()) @@ -878,7 +905,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) diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index cda35913aa9..18e3d26d1fb 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -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 { 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(); diff --git a/src/cargo/util/command_prelude.rs b/src/cargo/util/command_prelude.rs index e703128c073..1e43e90df81 100644 --- a/src/cargo/util/command_prelude.rs +++ b/src/cargo/util/command_prelude.rs @@ -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"] { diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index b1c311ec9f7..b0295a7245e 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -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, repository: Option, metadata: Option, + resolver: Option, } #[derive(Debug, Deserialize, Serialize)] @@ -813,6 +815,7 @@ pub struct TomlWorkspace { #[serde(rename = "default-members")] default_members: Option>, exclude: Option>, + resolver: Option, } 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 { + 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, )) } diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 4866e56992d..eaf05b70c43 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -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) diff --git a/tests/testsuite/features2.rs b/tests/testsuite/features2.rs index d85b0b13a70..e6f729263fe 100644 --- a/tests/testsuite/features2.rs +++ b/tests/testsuite/features2.rs @@ -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)], + ); +} diff --git a/tests/testsuite/package.rs b/tests/testsuite/package.rs index 16c79fcfa5f..c25d79c248c 100644 --- a/tests/testsuite/package.rs +++ b/tests/testsuite/package.rs @@ -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)], ); }