From 6f4f383e05ea8e2d0e11da4b194e28eea8bd7815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20BRANSTETT?= Date: Sat, 26 Feb 2022 13:51:37 +0100 Subject: [PATCH 1/7] Add unstable documentation dependencies under [doc-dependencies] --- src/bin/cargo/commands/tree.rs | 11 +++++- src/cargo/core/compiler/unit_dependencies.rs | 33 +++++++++++------ src/cargo/core/dependency.rs | 4 +- src/cargo/core/features.rs | 3 ++ src/cargo/core/resolver/dep_cache.rs | 2 +- src/cargo/core/summary.rs | 9 ++++- src/cargo/ops/cargo_compile.rs | 3 +- src/cargo/ops/fix.rs | 1 + src/cargo/ops/registry.rs | 3 +- src/cargo/ops/tree/graph.rs | 1 + src/cargo/ops/tree/mod.rs | 2 + src/cargo/sources/registry/mod.rs | 1 + src/cargo/util/toml/mod.rs | 39 ++++++++++++++++++++ src/doc/src/reference/unstable.md | 32 ++++++++++++++++ 14 files changed, 125 insertions(+), 19 deletions(-) diff --git a/src/bin/cargo/commands/tree.rs b/src/bin/cargo/commands/tree.rs index c1d47cbe5cb..ec501cb5d44 100644 --- a/src/bin/cargo/commands/tree.rs +++ b/src/bin/cargo/commands/tree.rs @@ -238,7 +238,7 @@ fn parse_edge_kinds(config: &Config, args: &ArgMatches) -> CargoResult<(HashSet< } if kinds.is_empty() { - kinds.extend(&["normal", "build", "dev"]); + kinds.extend(&["normal", "build", "dev", "doc"]); } (kinds, no_proc_macro) @@ -249,8 +249,10 @@ fn parse_edge_kinds(config: &Config, args: &ArgMatches) -> CargoResult<(HashSet< result.insert(EdgeKind::Dep(DepKind::Normal)); result.insert(EdgeKind::Dep(DepKind::Build)); result.insert(EdgeKind::Dep(DepKind::Development)); + result.insert(EdgeKind::Dep(DepKind::Documentation)); }; let unknown = |k| { + // FIXME: Should include no-doc when stable bail!( "unknown edge kind `{}`, valid values are \ \"normal\", \"build\", \"dev\", \ @@ -266,8 +268,10 @@ fn parse_edge_kinds(config: &Config, args: &ArgMatches) -> CargoResult<(HashSet< "no-normal" => result.remove(&EdgeKind::Dep(DepKind::Normal)), "no-build" => result.remove(&EdgeKind::Dep(DepKind::Build)), "no-dev" => result.remove(&EdgeKind::Dep(DepKind::Development)), + "no-doc" => result.remove(&EdgeKind::Dep(DepKind::Documentation)), "features" => result.insert(EdgeKind::Feature), - "normal" | "build" | "dev" | "all" => { + "normal" | "build" | "dev" | "doc" | "all" => { + // FIXME: Should include no-doc when stable bail!( "`{}` dependency kind cannot be mixed with \ \"no-normal\", \"no-build\", or \"no-dev\" \ @@ -298,6 +302,9 @@ fn parse_edge_kinds(config: &Config, args: &ArgMatches) -> CargoResult<(HashSet< "dev" => { result.insert(EdgeKind::Dep(DepKind::Development)); } + "doc" => { + result.insert(EdgeKind::Dep(DepKind::Documentation)); + } k => return unknown(k), } } diff --git a/src/cargo/core/compiler/unit_dependencies.rs b/src/cargo/core/compiler/unit_dependencies.rs index 857436e9f90..7aece18de2c 100644 --- a/src/cargo/core/compiler/unit_dependencies.rs +++ b/src/cargo/core/compiler/unit_dependencies.rs @@ -57,7 +57,7 @@ struct State<'a, 'cfg> { /// A set of edges in `unit_dependencies` where (a, b) means that the /// dependency from a to b was added purely because it was a dev-dependency. /// This is used during `connect_run_custom_build_deps`. - dev_dependency_edges: HashSet<(Unit, Unit)>, + transitive_dependency_edges: HashSet<(Unit, Unit)>, } /// A boolean-like to indicate if a `Unit` is an artifact or not. @@ -112,7 +112,7 @@ pub fn build_unit_dependencies<'a, 'cfg>( profiles, interner, scrape_units, - dev_dependency_edges: HashSet::new(), + transitive_dependency_edges: HashSet::new(), }; let std_unit_deps = calc_deps_of_std(&mut state, std_roots)?; @@ -325,7 +325,7 @@ fn compute_deps( } } } - state.dev_dependency_edges.extend(dev_deps); + state.transitive_dependency_edges.extend(dev_deps); // If this target is a build script, then what we've collected so far is // all we need. If this isn't a build script, then it depends on the @@ -992,7 +992,7 @@ fn connect_run_custom_build_deps(state: &mut State<'_, '_>) { // be cyclic we could have cyclic build-script executions. .filter_map(move |(parent, other)| { if state - .dev_dependency_edges + .transitive_dependency_edges .contains(&((*parent).clone(), other.unit.clone())) { None @@ -1087,14 +1087,23 @@ impl<'a, 'cfg> State<'a, 'cfg> { } // If this dependency is **not** a transitive dependency, then it - // only applies to test/example targets. - if !dep.is_transitive() - && !unit.target.is_test() - && !unit.target.is_example() - && !unit.mode.is_doc_scrape() - && !unit.mode.is_any_test() - { - return false; + // only applies to test/example or doc/doctest targets. + match dep.kind() { + DepKind::Development => { + if !unit.target.is_test() + && !unit.target.is_example() + && !unit.mode.is_doc_scrape() + && !unit.mode.is_any_test() + { + return false; + } + } + DepKind::Documentation => { + if !unit.mode.is_doc() && !unit.mode.is_doc_test() { + return false; + } + } + DepKind::Normal | DepKind::Build => {} } // If this dependency is only available for certain platforms, diff --git a/src/cargo/core/dependency.rs b/src/cargo/core/dependency.rs index b86d2e93194..ea88d9f0a9d 100644 --- a/src/cargo/core/dependency.rs +++ b/src/cargo/core/dependency.rs @@ -103,6 +103,7 @@ impl ser::Serialize for Dependency { pub enum DepKind { Normal, Development, + Documentation, Build, } @@ -114,6 +115,7 @@ impl ser::Serialize for DepKind { match *self { DepKind::Normal => None, DepKind::Development => Some("dev"), + DepKind::Documentation => Some("doc"), DepKind::Build => Some("build"), } .serialize(s) @@ -369,7 +371,7 @@ impl Dependency { pub fn is_transitive(&self) -> bool { match self.inner.kind { DepKind::Normal | DepKind::Build => true, - DepKind::Development => false, + DepKind::Development | DepKind::Documentation => false, } } diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index cc8d0a3cdbe..cbea5cadc66 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -412,6 +412,9 @@ features! { // Allow specifying rustflags directly in a profile (unstable, profile_rustflags, "", "reference/unstable.html#profile-rustflags-option"), + + // Allow specifying dependencies under [doc-dependencies] + (unstable, doc_dependencies, "", "reference/unstable.html#documentation-dependencies"), } pub struct Feature { diff --git a/src/cargo/core/resolver/dep_cache.rs b/src/cargo/core/resolver/dep_cache.rs index d3ad787d589..0986df38373 100644 --- a/src/cargo/core/resolver/dep_cache.rs +++ b/src/cargo/core/resolver/dep_cache.rs @@ -247,7 +247,7 @@ pub fn resolve_features<'b>( s: &'b Summary, opts: &'b ResolveOpts, ) -> ActivateResult<(HashSet, Vec<(Dependency, FeaturesSet)>)> { - // First, filter by dev-dependencies. + // First, filter by dev-dependencies or doc-dependencies. let deps = s.dependencies(); let deps = deps.iter().filter(|d| d.is_transitive() || opts.dev_deps); diff --git a/src/cargo/core/summary.rs b/src/cargo/core/summary.rs index 9c5396eecf5..3c570565961 100644 --- a/src/cargo/core/summary.rs +++ b/src/cargo/core/summary.rs @@ -1,3 +1,4 @@ +use crate::core::dependency::DepKind; use crate::core::{Dependency, PackageId, SourceId}; use crate::util::interning::InternedString; use crate::util::{CargoResult, Config}; @@ -41,8 +42,14 @@ impl Summary { for dep in dependencies.iter() { let dep_name = dep.name_in_toml(); if dep.is_optional() && !dep.is_transitive() { + let kind = match dep.kind() { + DepKind::Documentation => "doc", + DepKind::Development => "dev", + _ => unreachable!(), + }; bail!( - "dev-dependencies are not allowed to be optional: `{}`", + "{}-dependencies are not allowed to be optional: `{}`", + kind, dep_name ) } diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 3b247c9f7a7..fce239d2789 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -444,9 +444,10 @@ pub fn create_bcx<'a, 'cfg>( && !ws.is_member(pkg) && pkg.dependencies().iter().any(|dep| !dep.is_transitive()) { + // FIXME: Should include doc-dependencies when stable anyhow::bail!( "package `{}` cannot be tested because it requires dev-dependencies \ - and is not a member of the workspace", + and is not a member of the workspace", pkg.name() ); } diff --git a/src/cargo/ops/fix.rs b/src/cargo/ops/fix.rs index 75b2d011e4f..80f46ef5a58 100644 --- a/src/cargo/ops/fix.rs +++ b/src/cargo/ops/fix.rs @@ -299,6 +299,7 @@ fn check_resolver_change(ws: &Workspace<'_>, opts: &FixOptions) -> CargoResult<( show_diffs(without_dev_diffs); } if !with_dev_diffs.is_empty() { + // FIXME: Include doc-dependencies when stable drop_eprintln!( config, "The following differences only apply when building with dev-dependencies:\n" diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index 2c67091ec85..6de477cd866 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -183,7 +183,7 @@ fn transmit( .dependencies() .iter() .filter(|dep| { - // Skip dev-dependency without version. + // Skip {dev,doc}-dependency without version. dep.is_transitive() || dep.specified_req() }) .map(|dep| { @@ -212,6 +212,7 @@ fn transmit( DepKind::Normal => "normal", DepKind::Build => "build", DepKind::Development => "dev", + DepKind::Documentation => "doc", } .to_string(), registry: dep_registry, diff --git a/src/cargo/ops/tree/graph.rs b/src/cargo/ops/tree/graph.rs index bf254e49888..35709355574 100644 --- a/src/cargo/ops/tree/graph.rs +++ b/src/cargo/ops/tree/graph.rs @@ -329,6 +329,7 @@ fn add_pkg( (_, DepKind::Build) => CompileKind::Host, (_, DepKind::Normal) => node_kind, (_, DepKind::Development) => node_kind, + (_, DepKind::Documentation) => node_kind, }; // Filter out inactivated targets. if !show_all_targets && !target_data.dep_platform_activated(dep, kind) { diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs index 4959e04e595..80e41a6af3e 100644 --- a/src/cargo/ops/tree/mod.rs +++ b/src/cargo/ops/tree/mod.rs @@ -328,6 +328,7 @@ fn print_node<'a>( EdgeKind::Dep(DepKind::Normal), EdgeKind::Dep(DepKind::Build), EdgeKind::Dep(DepKind::Development), + EdgeKind::Dep(DepKind::Documentation), EdgeKind::Feature, ] { print_dependencies( @@ -376,6 +377,7 @@ fn print_dependencies<'a>( EdgeKind::Dep(DepKind::Normal) => None, EdgeKind::Dep(DepKind::Build) => Some("[build-dependencies]"), EdgeKind::Dep(DepKind::Development) => Some("[dev-dependencies]"), + EdgeKind::Dep(DepKind::Documentation) => Some("[doc-dependencies]"), EdgeKind::Feature => None, }; diff --git a/src/cargo/sources/registry/mod.rs b/src/cargo/sources/registry/mod.rs index d9df11bbfd2..9d25cb7c018 100644 --- a/src/cargo/sources/registry/mod.rs +++ b/src/cargo/sources/registry/mod.rs @@ -382,6 +382,7 @@ impl<'a> RegistryDependency<'a> { } let kind = match kind.as_deref().unwrap_or("") { "dev" => DepKind::Development, + "doc" => DepKind::Documentation, "build" => DepKind::Build, _ => DepKind::Normal, }; diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 85eb64ede96..2cce1a382c5 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -328,6 +328,9 @@ pub struct TomlManifest { dev_dependencies: Option>, #[serde(rename = "dev_dependencies")] dev_dependencies2: Option>, + doc_dependencies: Option>, + #[serde(rename = "doc_dependencies")] + doc_dependencies2: Option>, build_dependencies: Option>, #[serde(rename = "build_dependencies")] build_dependencies2: Option>, @@ -1026,6 +1029,14 @@ impl TomlManifest { TomlDependency::is_version_specified, )?, dev_dependencies2: None, + doc_dependencies: map_deps( + config, + self.doc_dependencies + .as_ref() + .or_else(|| self.doc_dependencies2.as_ref()), + TomlDependency::is_version_specified, + )?, + doc_dependencies2: None, build_dependencies: map_deps( config, self.build_dependencies @@ -1051,6 +1062,14 @@ impl TomlManifest { TomlDependency::is_version_specified, )?, dev_dependencies2: None, + doc_dependencies: map_deps( + config, + v.doc_dependencies + .as_ref() + .or_else(|| v.doc_dependencies2.as_ref()), + TomlDependency::is_version_specified, + )?, + doc_dependencies2: None, build_dependencies: map_deps( config, v.build_dependencies @@ -1291,6 +1310,14 @@ impl TomlManifest { .as_ref() .or_else(|| me.dev_dependencies2.as_ref()); process_dependencies(&mut cx, dev_deps, Some(DepKind::Development))?; + let doc_deps = me + .doc_dependencies + .as_ref() + .or_else(|| me.doc_dependencies2.as_ref()); + if doc_deps.filter(|doc_deps| !doc_deps.is_empty()).is_some() { + features.require(Feature::doc_dependencies())?; + } + process_dependencies(&mut cx, doc_deps, Some(DepKind::Documentation))?; let build_deps = me .build_dependencies .as_ref() @@ -1314,6 +1341,14 @@ impl TomlManifest { .as_ref() .or_else(|| platform.dev_dependencies2.as_ref()); process_dependencies(&mut cx, dev_deps, Some(DepKind::Development))?; + let doc_deps = platform + .doc_dependencies + .as_ref() + .or_else(|| platform.doc_dependencies2.as_ref()); + if doc_deps.filter(|doc_deps| !doc_deps.is_empty()).is_some() { + features.require(Feature::doc_dependencies())?; + } + process_dependencies(&mut cx, doc_deps, Some(DepKind::Documentation))?; } replace = me.replace(&mut cx)?; @@ -2073,6 +2108,10 @@ struct TomlPlatform { dev_dependencies: Option>, #[serde(rename = "dev_dependencies")] dev_dependencies2: Option>, + #[serde(rename = "doc-dependencies")] + doc_dependencies: Option>, + #[serde(rename = "doc_dependencies")] + doc_dependencies2: Option>, } impl TomlTarget { diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 6de7da90100..72e9bcc42f1 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -1141,6 +1141,38 @@ For instance: cargo check -Z unstable-options -Z check-cfg-features ``` +### Documentation dependencies + +* Tracking Issue: [#XXXX](https://github.com/rust-lang/cargo/issues/XXX) +* PR: [#XXXX](https://github.com/rust-lang/cargo/pull/XXXX) + +The `doc-dependencies` feature allows documentation specific dependencies. +You can add a [doc-dependencies] section to your Cargo.toml whose format is equivalent to [dependencies]: + +``` +cargo-features = ["doc-dependencies"] + +[project] +name = "foo" +version = "0.0.1" + +[doc-dependencies] +tempdir = "0.3" +``` + +Doc-dependencies are not used when compiling a package for building, but are used for documenting +or running documentation tests. + +These dependencies are not propagated to other packages which depend on this package. + +You can also have target-specific documentation dependencies by using doc-dependencies in the target +section header instead of dependencies. For example: + +``` +[target.'cfg(unix)'.doc-dependencies] +mio = "0.0.1" +``` + ## Stabilized and removed features ### Compile progress From 65c026408c37a5ce766db85aac85f8ee04570dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20BRANSTETT?= Date: Sun, 27 Feb 2022 00:53:25 +0100 Subject: [PATCH 2/7] Rename HasDevUnits to HasTransitiveUnits as it now also control doc deps --- src/cargo/core/compiler/standard_lib.rs | 4 ++-- src/cargo/core/package.rs | 22 ++++++++++++---------- src/cargo/core/resolver/dep_cache.rs | 4 +++- src/cargo/core/resolver/features.rs | 13 ++++++++----- src/cargo/core/resolver/mod.rs | 4 ++-- src/cargo/core/resolver/types.rs | 13 ++++++++----- src/cargo/ops/cargo_compile.rs | 15 ++++++++------- src/cargo/ops/cargo_generate_lockfile.rs | 8 ++++---- src/cargo/ops/cargo_output_metadata.rs | 4 ++-- src/cargo/ops/fix.rs | 20 ++++++++++---------- src/cargo/ops/resolve.rs | 14 +++++++------- src/cargo/ops/tree/mod.rs | 9 ++++++--- 12 files changed, 72 insertions(+), 58 deletions(-) diff --git a/src/cargo/core/compiler/standard_lib.rs b/src/cargo/core/compiler/standard_lib.rs index 94242fdb8fd..4aa26d44bca 100644 --- a/src/cargo/core/compiler/standard_lib.rs +++ b/src/cargo/core/compiler/standard_lib.rs @@ -5,7 +5,7 @@ use crate::core::compiler::UnitInterner; use crate::core::compiler::{CompileKind, CompileMode, RustcTargetData, Unit}; use crate::core::profiles::{Profiles, UnitFor}; use crate::core::resolver::features::{CliFeatures, FeaturesFor, ResolvedFeatures}; -use crate::core::resolver::HasDevUnits; +use crate::core::resolver::HasTransitiveUnits; use crate::core::{Dependency, PackageId, PackageSet, Resolve, SourceId, Workspace}; use crate::ops::{self, Packages}; use crate::util::errors::CargoResult; @@ -116,7 +116,7 @@ pub fn resolve_std<'cfg>( requested_targets, &cli_features, &specs, - HasDevUnits::No, + HasTransitiveUnits::No, crate::core::resolver::features::ForceAllTargets::No, )?; Ok(( diff --git a/src/cargo/core/package.rs b/src/cargo/core/package.rs index e8685610aeb..82617878f3f 100644 --- a/src/cargo/core/package.rs +++ b/src/cargo/core/package.rs @@ -21,7 +21,7 @@ use toml_edit::easy as toml; use crate::core::compiler::{CompileKind, RustcTargetData}; use crate::core::dependency::DepKind; use crate::core::resolver::features::ForceAllTargets; -use crate::core::resolver::{HasDevUnits, Resolve}; +use crate::core::resolver::{HasTransitiveUnits, Resolve}; use crate::core::source::MaybePackage; use crate::core::{Dependency, Manifest, PackageId, SourceId, Target}; use crate::core::{SourceMap, Summary, Workspace}; @@ -494,7 +494,7 @@ impl<'cfg> PackageSet<'cfg> { &self, resolve: &Resolve, root_ids: &[PackageId], - has_dev_units: HasDevUnits, + has_transitive_units: HasTransitiveUnits, requested_kinds: &[CompileKind], target_data: &RustcTargetData<'cfg>, force_all_targets: ForceAllTargets, @@ -503,7 +503,7 @@ impl<'cfg> PackageSet<'cfg> { used: &mut BTreeSet, resolve: &Resolve, pkg_id: PackageId, - has_dev_units: HasDevUnits, + has_transitive_units: HasTransitiveUnits, requested_kinds: &[CompileKind], target_data: &RustcTargetData<'_>, force_all_targets: ForceAllTargets, @@ -514,7 +514,7 @@ impl<'cfg> PackageSet<'cfg> { let filtered_deps = PackageSet::filter_deps( pkg_id, resolve, - has_dev_units, + has_transitive_units, requested_kinds, target_data, force_all_targets, @@ -524,7 +524,7 @@ impl<'cfg> PackageSet<'cfg> { used, resolve, pkg_id, - has_dev_units, + has_transitive_units, requested_kinds, target_data, force_all_targets, @@ -543,7 +543,7 @@ impl<'cfg> PackageSet<'cfg> { &mut to_download, resolve, *id, - has_dev_units, + has_transitive_units, requested_kinds, target_data, force_all_targets, @@ -560,7 +560,7 @@ impl<'cfg> PackageSet<'cfg> { ws: &Workspace<'cfg>, resolve: &Resolve, root_ids: &[PackageId], - has_dev_units: HasDevUnits, + has_transitive_units: HasTransitiveUnits, requested_kinds: &[CompileKind], target_data: &RustcTargetData<'_>, force_all_targets: ForceAllTargets, @@ -571,7 +571,7 @@ impl<'cfg> PackageSet<'cfg> { let dep_pkgs_to_deps: Vec<_> = PackageSet::filter_deps( root_id, resolve, - has_dev_units, + has_transitive_units, requested_kinds, target_data, force_all_targets, @@ -612,7 +612,7 @@ impl<'cfg> PackageSet<'cfg> { fn filter_deps<'a>( pkg_id: PackageId, resolve: &'a Resolve, - has_dev_units: HasDevUnits, + has_transitive_units: HasTransitiveUnits, requested_kinds: &'a [CompileKind], target_data: &'a RustcTargetData<'_>, force_all_targets: ForceAllTargets, @@ -621,7 +621,9 @@ impl<'cfg> PackageSet<'cfg> { .deps(pkg_id) .filter(move |&(_id, deps)| { deps.iter().any(|dep| { - if dep.kind() == DepKind::Development && has_dev_units == HasDevUnits::No { + if (dep.kind() == DepKind::Development || dep.kind() == DepKind::Documentation) + && has_transitive_units == HasTransitiveUnits::No + { return false; } if force_all_targets == ForceAllTargets::No { diff --git a/src/cargo/core/resolver/dep_cache.rs b/src/cargo/core/resolver/dep_cache.rs index 0986df38373..3b13bd6967c 100644 --- a/src/cargo/core/resolver/dep_cache.rs +++ b/src/cargo/core/resolver/dep_cache.rs @@ -249,7 +249,9 @@ pub fn resolve_features<'b>( ) -> ActivateResult<(HashSet, Vec<(Dependency, FeaturesSet)>)> { // First, filter by dev-dependencies or doc-dependencies. let deps = s.dependencies(); - let deps = deps.iter().filter(|d| d.is_transitive() || opts.dev_deps); + let deps = deps + .iter() + .filter(|d| d.is_transitive() || opts.transitive_deps); let reqs = build_requirements(parent, s, opts)?; let mut ret = Vec::new(); diff --git a/src/cargo/core/resolver/features.rs b/src/cargo/core/resolver/features.rs index 41e497c0354..6786bb9e75b 100644 --- a/src/cargo/core/resolver/features.rs +++ b/src/cargo/core/resolver/features.rs @@ -86,7 +86,7 @@ pub struct FeatureOpts { /// `cargo test` because the lib may need to be built 3 times instead of /// twice. #[derive(Copy, Clone, PartialEq)] -pub enum HasDevUnits { +pub enum HasTransitiveUnits { Yes, No, } @@ -163,7 +163,7 @@ impl FeaturesFor { impl FeatureOpts { pub fn new( ws: &Workspace<'_>, - has_dev_units: HasDevUnits, + has_dev_units: HasTransitiveUnits, force_all_targets: ForceAllTargets, ) -> CargoResult { let mut opts = FeatureOpts::default(); @@ -195,7 +195,7 @@ impl FeatureOpts { enable(&vec!["all".to_string()]).unwrap(); } } - if let HasDevUnits::Yes = has_dev_units { + if let HasTransitiveUnits::Yes = has_dev_units { // Dev deps cannot be decoupled when they are in use. opts.decouple_dev_deps = false; } @@ -206,12 +206,15 @@ impl FeatureOpts { } /// Creates a new FeatureOpts for the given behavior. - pub fn new_behavior(behavior: ResolveBehavior, has_dev_units: HasDevUnits) -> FeatureOpts { + pub fn new_behavior( + behavior: ResolveBehavior, + has_dev_units: HasTransitiveUnits, + ) -> FeatureOpts { match behavior { ResolveBehavior::V1 => FeatureOpts::default(), ResolveBehavior::V2 => FeatureOpts { decouple_host_deps: true, - decouple_dev_deps: has_dev_units == HasDevUnits::No, + decouple_dev_deps: has_dev_units == HasTransitiveUnits::No, ignore_inactive_targets: true, compare: false, }, diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index 28b328132d2..b677d8f8dbf 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -69,7 +69,7 @@ use self::types::{FeaturesSet, RcVecIter, RemainingDeps, ResolverProgress}; pub use self::encode::Metadata; pub use self::encode::{EncodableDependency, EncodablePackageId, EncodableResolve}; pub use self::errors::{ActivateError, ActivateResult, ResolveError}; -pub use self::features::{CliFeatures, ForceAllTargets, HasDevUnits}; +pub use self::features::{CliFeatures, ForceAllTargets, HasTransitiveUnits}; pub use self::resolve::{Resolve, ResolveVersion}; pub use self::types::{ResolveBehavior, ResolveOpts}; pub use self::version_prefs::{VersionOrdering, VersionPreferences}; @@ -378,7 +378,7 @@ fn activate_deps_loop( let pid = candidate.package_id(); let opts = ResolveOpts { - dev_deps: false, + transitive_deps: false, features: RequestedFeatures::DepFeatures { features: Rc::clone(&features), uses_default_features: dep.uses_default_features(), diff --git a/src/cargo/core/resolver/types.rs b/src/cargo/core/resolver/types.rs index 4617e3ea8e5..ecd86e975f1 100644 --- a/src/cargo/core/resolver/types.rs +++ b/src/cargo/core/resolver/types.rs @@ -130,11 +130,11 @@ impl ResolveBehavior { /// Options for how the resolve should work. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct ResolveOpts { - /// Whether or not dev-dependencies should be included. + /// Whether or not {doc,dev}-dependencies should be included. /// /// This may be set to `false` by things like `cargo install` or `-Z avoid-dev-deps`. /// It also gets set to `false` when activating dependencies in the resolver. - pub dev_deps: bool, + pub transitive_deps: bool, /// Set of features requested on the command-line. pub features: RequestedFeatures, } @@ -143,13 +143,16 @@ impl ResolveOpts { /// Creates a ResolveOpts that resolves everything. pub fn everything() -> ResolveOpts { ResolveOpts { - dev_deps: true, + transitive_deps: true, features: RequestedFeatures::CliFeatures(CliFeatures::new_all(true)), } } - pub fn new(dev_deps: bool, features: RequestedFeatures) -> ResolveOpts { - ResolveOpts { dev_deps, features } + pub fn new(transitive_deps: bool, features: RequestedFeatures) -> ResolveOpts { + ResolveOpts { + transitive_deps, + features, + } } } diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index fce239d2789..f334165921c 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -34,7 +34,7 @@ use crate::core::compiler::{CompileKind, CompileMode, CompileTarget, RustcTarget use crate::core::compiler::{DefaultExecutor, Executor, UnitInterner}; use crate::core::profiles::{Profiles, UnitFor}; use crate::core::resolver::features::{self, CliFeatures, FeaturesFor}; -use crate::core::resolver::{HasDevUnits, Resolve}; +use crate::core::resolver::{HasTransitiveUnits, Resolve}; use crate::core::{FeatureValue, Package, PackageSet, Shell, Summary, Target}; use crate::core::{PackageId, PackageIdSpec, SourceId, TargetKind, Workspace}; use crate::drop_println; @@ -377,18 +377,19 @@ pub fn create_bcx<'a, 'cfg>( }; let resolve_specs = full_specs.to_package_id_specs(ws)?; - let has_dev_units = if filter.need_dev_deps(build_config.mode) || need_reverse_dependencies { - HasDevUnits::Yes - } else { - HasDevUnits::No - }; + let has_transitive_units = + if filter.need_dev_deps(build_config.mode) || need_reverse_dependencies { + HasTransitiveUnits::Yes + } else { + HasTransitiveUnits::No + }; let resolve = ops::resolve_ws_with_opts( ws, &target_data, &build_config.requested_kinds, cli_features, &resolve_specs, - has_dev_units, + has_transitive_units, crate::core::resolver::features::ForceAllTargets::No, )?; let WorkspaceResolve { diff --git a/src/cargo/ops/cargo_generate_lockfile.rs b/src/cargo/ops/cargo_generate_lockfile.rs index 04d4010f45e..ae5d242c59c 100644 --- a/src/cargo/ops/cargo_generate_lockfile.rs +++ b/src/cargo/ops/cargo_generate_lockfile.rs @@ -1,5 +1,5 @@ use crate::core::registry::PackageRegistry; -use crate::core::resolver::features::{CliFeatures, HasDevUnits}; +use crate::core::resolver::features::{CliFeatures, HasTransitiveUnits}; use crate::core::{PackageId, PackageIdSpec}; use crate::core::{Resolve, SourceId, Workspace}; use crate::ops; @@ -25,7 +25,7 @@ pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> { &mut registry, ws, &CliFeatures::new_all(true), - HasDevUnits::Yes, + HasTransitiveUnits::Yes, None, None, &[], @@ -62,7 +62,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes &mut registry, ws, &CliFeatures::new_all(true), - HasDevUnits::Yes, + HasTransitiveUnits::Yes, None, None, &[], @@ -120,7 +120,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes &mut registry, ws, &CliFeatures::new_all(true), - HasDevUnits::Yes, + HasTransitiveUnits::Yes, Some(&previous_resolve), Some(&to_avoid), &[], diff --git a/src/cargo/ops/cargo_output_metadata.rs b/src/cargo/ops/cargo_output_metadata.rs index d8a86eae778..ccc504fdca8 100644 --- a/src/cargo/ops/cargo_output_metadata.rs +++ b/src/cargo/ops/cargo_output_metadata.rs @@ -1,7 +1,7 @@ use crate::core::compiler::{CompileKind, RustcTargetData}; use crate::core::dependency::DepKind; use crate::core::package::SerializedPackage; -use crate::core::resolver::{features::CliFeatures, HasDevUnits, Resolve}; +use crate::core::resolver::{features::CliFeatures, HasTransitiveUnits, Resolve}; use crate::core::{Dependency, Package, PackageId, Workspace}; use crate::ops::{self, Packages}; use crate::util::interning::InternedString; @@ -127,7 +127,7 @@ fn build_resolve_graph( &requested_kinds, &metadata_opts.cli_features, &specs, - HasDevUnits::Yes, + HasTransitiveUnits::Yes, force_all, )?; diff --git a/src/cargo/ops/fix.rs b/src/cargo/ops/fix.rs index 80f46ef5a58..e8459d634ed 100644 --- a/src/cargo/ops/fix.rs +++ b/src/cargo/ops/fix.rs @@ -54,7 +54,7 @@ use semver::Version; use crate::core::compiler::RustcTargetData; use crate::core::resolver::features::{DiffMap, FeatureOpts, FeatureResolver, FeaturesFor}; -use crate::core::resolver::{HasDevUnits, Resolve, ResolveBehavior}; +use crate::core::resolver::{HasTransitiveUnits, Resolve, ResolveBehavior}; use crate::core::{Edition, MaybePackage, PackageId, Workspace}; use crate::ops::resolve::WorkspaceResolve; use crate::ops::{self, CompileOptions}; @@ -257,14 +257,14 @@ fn check_resolver_change(ws: &Workspace<'_>, opts: &FixOptions) -> CargoResult<( let diffs = v2_features.compare_legacy(&ws_resolve.resolved_features); Ok((ws_resolve, diffs)) }; - let (_, without_dev_diffs) = resolve_differences(HasDevUnits::No)?; - let (ws_resolve, mut with_dev_diffs) = resolve_differences(HasDevUnits::Yes)?; - if without_dev_diffs.is_empty() && with_dev_diffs.is_empty() { + let (_, without_transitive_diffs) = resolve_differences(HasTransitiveUnits::No)?; + let (ws_resolve, mut with_transitive_diffs) = resolve_differences(HasTransitiveUnits::Yes)?; + if without_transitive_diffs.is_empty() && with_transitive_diffs.is_empty() { // Nothing is different, nothing to report. return Ok(()); } // Only display unique changes with dev-dependencies. - with_dev_diffs.retain(|k, vals| without_dev_diffs.get(k) != Some(vals)); + with_transitive_diffs.retain(|k, vals| without_transitive_diffs.get(k) != Some(vals)); let config = ws.config(); config.shell().note( "Switching to Edition 2021 will enable the use of the version 2 feature resolver in Cargo.", @@ -295,16 +295,16 @@ fn check_resolver_change(ws: &Workspace<'_>, opts: &FixOptions) -> CargoResult<( } drop_eprint!(config, "\n"); }; - if !without_dev_diffs.is_empty() { - show_diffs(without_dev_diffs); + if !without_transitive_diffs.is_empty() { + show_diffs(without_transitive_diffs); } - if !with_dev_diffs.is_empty() { + if !with_transitive_diffs.is_empty() { // FIXME: Include doc-dependencies when stable drop_eprintln!( config, - "The following differences only apply when building with dev-dependencies:\n" + "The following differences only apply when building with dev-dependencies or doc-dependencies:\n" ); - show_diffs(with_dev_diffs); + show_diffs(with_transitive_diffs); } report_maybe_diesel(config, &ws_resolve.targeted_resolve)?; Ok(()) diff --git a/src/cargo/ops/resolve.rs b/src/cargo/ops/resolve.rs index 3f96e262934..c062139f170 100644 --- a/src/cargo/ops/resolve.rs +++ b/src/cargo/ops/resolve.rs @@ -16,7 +16,7 @@ use crate::core::resolver::features::{ CliFeatures, FeatureOpts, FeatureResolver, ForceAllTargets, RequestedFeatures, ResolvedFeatures, }; use crate::core::resolver::{ - self, HasDevUnits, Resolve, ResolveOpts, ResolveVersion, VersionPreferences, + self, HasTransitiveUnits, Resolve, ResolveOpts, ResolveVersion, VersionPreferences, }; use crate::core::summary::Summary; use crate::core::Feature; @@ -85,7 +85,7 @@ pub fn resolve_ws_with_opts<'cfg>( requested_targets: &[CompileKind], cli_features: &CliFeatures, specs: &[PackageIdSpec], - has_dev_units: HasDevUnits, + has_dev_units: HasTransitiveUnits, force_all_targets: ForceAllTargets, ) -> CargoResult> { let mut registry = PackageRegistry::new(ws.config())?; @@ -198,7 +198,7 @@ fn resolve_with_registry<'cfg>( registry, ws, &CliFeatures::new_all(true), - HasDevUnits::Yes, + HasTransitiveUnits::Yes, prev.as_ref(), None, &[], @@ -230,7 +230,7 @@ pub fn resolve_with_previous<'cfg>( registry: &mut PackageRegistry<'cfg>, ws: &Workspace<'cfg>, cli_features: &CliFeatures, - has_dev_units: HasDevUnits, + has_dev_units: HasTransitiveUnits, previous: Option<&Resolve>, to_avoid: Option<&HashSet>, specs: &[PackageIdSpec], @@ -387,13 +387,13 @@ pub fn resolve_with_previous<'cfg>( let keep = |p: &PackageId| pre_patch_keep(p) && !avoid_patch_ids.contains(p); - let dev_deps = ws.require_optional_deps() || has_dev_units == HasDevUnits::Yes; + let transitive_deps = ws.require_optional_deps() || has_dev_units == HasTransitiveUnits::Yes; // In the case where a previous instance of resolve is available, we // want to lock as many packages as possible to the previous version // without disturbing the graph structure. if let Some(r) = previous { trace!("previous: {:?}", r); - register_previous_locks(ws, registry, r, &keep, dev_deps); + register_previous_locks(ws, registry, r, &keep, transitive_deps); } // Prefer to use anything in the previous lock file, aka we want to have conservative updates. @@ -422,7 +422,7 @@ pub fn resolve_with_previous<'cfg>( ( summary, ResolveOpts { - dev_deps, + transitive_deps, features: RequestedFeatures::CliFeatures(features), }, ) diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs index 80e41a6af3e..ee069b974b8 100644 --- a/src/cargo/ops/tree/mod.rs +++ b/src/cargo/ops/tree/mod.rs @@ -3,7 +3,7 @@ use self::format::Pattern; use crate::core::compiler::{CompileKind, RustcTargetData}; use crate::core::dependency::DepKind; -use crate::core::resolver::{features::CliFeatures, ForceAllTargets, HasDevUnits}; +use crate::core::resolver::{features::CliFeatures, ForceAllTargets, HasTransitiveUnits}; use crate::core::{Package, PackageId, PackageIdSpec, Workspace}; use crate::ops::{self, Packages}; use crate::util::{CargoResult, Config}; @@ -140,10 +140,13 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<() let has_dev = if opts .edge_kinds .contains(&EdgeKind::Dep(DepKind::Development)) + || opts + .edge_kinds + .contains(&EdgeKind::Dep(DepKind::Documentation)) { - HasDevUnits::Yes + HasTransitiveUnits::Yes } else { - HasDevUnits::No + HasTransitiveUnits::No }; let force_all = if opts.target == Target::All { ForceAllTargets::Yes From 33c9dc2a5550b1a002fe26d842f5ae81ad50e005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20BRANSTETT?= Date: Mon, 28 Feb 2022 22:50:12 +0100 Subject: [PATCH 3/7] Add a bunch tests for [doc-dependencies] --- tests/testsuite/build.rs | 93 +++++++++++++++++++++++++ tests/testsuite/build_script.rs | 76 ++++++++++++++++++++ tests/testsuite/doc.rs | 41 +++++++++++ tests/testsuite/features.rs | 101 +++++++++++++++++++++++++++ tests/testsuite/features2.rs | 60 ++++++++++++++++ tests/testsuite/freshness.rs | 69 +++++++++++++++++++ tests/testsuite/install.rs | 30 ++++++++ tests/testsuite/metadata.rs | 11 ++- tests/testsuite/path.rs | 118 ++++++++++++++++++++++++++++++++ tests/testsuite/pub_priv.rs | 43 ++++++++++++ tests/testsuite/publish.rs | 88 ++++++++++++++++++++++++ tests/testsuite/tree.rs | 76 ++++++++++++++++++++ 12 files changed, 805 insertions(+), 1 deletion(-) diff --git a/tests/testsuite/build.rs b/tests/testsuite/build.rs index f404a61ab91..b77b802d903 100644 --- a/tests/testsuite/build.rs +++ b/tests/testsuite/build.rs @@ -2841,6 +2841,60 @@ fn cargo_platform_specific_dependency() { p.cargo("test").run(); } +#[cargo_test] +fn cargo_platform_specific_dependency_with_doc() { + let host = rustc_host(); + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + cargo-features = ["doc-dependencies"] + + [project] + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + build = "build.rs" + + [target.{host}.dependencies] + dep = {{ path = "dep" }} + [target.{host}.build-dependencies] + build = {{ path = "build" }} + [target.{host}.dev-dependencies] + dev = {{ path = "dev" }} + [target.{host}.doc-dependencies] + doc = {{ path = "doc" }} + "#, + host = host + ), + ) + .file("src/main.rs", "extern crate dep; fn main() { dep::dep() }") + .file( + "tests/foo.rs", + "extern crate dev; #[test] fn foo() { dev::dev() }", + ) + .file( + "build.rs", + "extern crate build; fn main() { build::build(); }", + ) + .file("dep/Cargo.toml", &basic_manifest("dep", "0.5.0")) + .file("dep/src/lib.rs", "pub fn dep() {}") + .file("build/Cargo.toml", &basic_manifest("build", "0.5.0")) + .file("build/src/lib.rs", "pub fn build() {}") + .file("dev/Cargo.toml", &basic_manifest("dev", "0.5.0")) + .file("dev/src/lib.rs", "pub fn dev() {}") + .file("doc/Cargo.toml", &basic_manifest("doc", "0.5.0")) + .file("doc/src/lib.rs", "pub fn doc() {}") + .build(); + + p.cargo("build").masquerade_as_nightly_cargo().run(); + + assert!(p.bin("foo").is_file()); + p.cargo("test").masquerade_as_nightly_cargo().run(); + p.cargo("doc").masquerade_as_nightly_cargo().run(); +} + #[cargo_test] fn bad_platform_specific_dependency() { let p = project() @@ -5106,6 +5160,45 @@ required by package `bar v0.1.0 ([..]/foo)` .run(); } +#[cargo_test] +fn avoid_doc_deps() { + Package::new("foo", "1.0.0").publish(); + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["doc-dependencies"] + + [package] + name = "bar" + version = "0.1.0" + authors = [] + + [doc-dependencies] + baz = "1.0.0" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("doc") + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr( + "\ +[UPDATING] [..] +[ERROR] no matching package named `baz` found +location searched: registry `crates-io` +required by package `bar v0.1.0 ([..]/foo)` +", + ) + .run(); + // FIXME: Should be something like -Zavoid-doc-deps + p.cargo("build -Zavoid-dev-deps") + .masquerade_as_nightly_cargo() + .run(); +} + #[cargo_test] fn default_cargo_config_jobs() { let p = project() diff --git a/tests/testsuite/build_script.rs b/tests/testsuite/build_script.rs index 40d8067d548..b7a3a35e5c9 100644 --- a/tests/testsuite/build_script.rs +++ b/tests/testsuite/build_script.rs @@ -2166,6 +2166,41 @@ fn test_dev_dep_build_script() { p.cargo("test").run(); } +#[cargo_test] +fn test_doc_dep_build_script() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["doc-dependencies"] + + [project] + name = "foo" + version = "0.5.0" + authors = [] + + [doc-dependencies.a] + path = "a" + "#, + ) + .file("src/lib.rs", "") + .file( + "a/Cargo.toml", + r#" + [project] + name = "a" + version = "0.5.0" + authors = [] + build = "build.rs" + "#, + ) + .file("a/build.rs", "fn main() {}") + .file("a/src/lib.rs", "") + .build(); + + p.cargo("doc").masquerade_as_nightly_cargo().run(); +} + #[cargo_test] fn build_script_with_dynamic_native_dependency() { let build = project() @@ -4655,6 +4690,47 @@ fn dev_dep_with_links() { p.cargo("check --tests").run() } +#[ignore] // FIXME: overflow it's stack +#[cargo_test] +fn doc_dep_with_links() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["doc-dependencies"] + + [package] + name = "foo" + version = "0.1.0" + authors = [] + links = "x" + + [doc-dependencies] + bar = { path = "./bar" } + "#, + ) + .file("build.rs", "fn main() {}") + .file("src/lib.rs", "") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.1.0" + authors = [] + links = "y" + + [dependencies] + foo = { path = ".." } + "#, + ) + .file("bar/build.rs", "fn main() {}") + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("doc").masquerade_as_nightly_cargo().run() +} + #[cargo_test] fn rerun_if_directory() { if !symlink_supported() { diff --git a/tests/testsuite/doc.rs b/tests/testsuite/doc.rs index 1e069c85f0b..96edb37c99a 100644 --- a/tests/testsuite/doc.rs +++ b/tests/testsuite/doc.rs @@ -38,6 +38,47 @@ fn simple() { assert!(p.root().join("target/doc/foo/index.html").is_file()); } +#[cargo_test] +fn simple_with_doc_deps() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["doc-dependencies"] + + [package] + name = "foo" + version = "0.0.1" + authors = [] + build = "build.rs" + + [doc-dependencies] + bar = { path = "bar/" } + "#, + ) + .file("build.rs", "fn main() {}") + .file("src/lib.rs", "pub fn foo() {}") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "pub fn bar() {}") + .build(); + + p.cargo("doc") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[..] foo v0.0.1 ([CWD]) +[..] bar v0.0.1 ([CWD]/bar) +[..] bar v0.0.1 ([CWD]/bar) +[..] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + assert!(p.root().join("target/doc").is_dir()); + assert!(p.root().join("target/doc/foo/index.html").is_file()); + assert!(p.root().join("target/doc/bar/index.html").is_file()); +} + #[cargo_test] fn doc_no_libs() { let p = project() diff --git a/tests/testsuite/features.rs b/tests/testsuite/features.rs index 8a8ed78e595..736f1650644 100644 --- a/tests/testsuite/features.rs +++ b/tests/testsuite/features.rs @@ -194,6 +194,41 @@ Caused by: .run(); } +#[cargo_test] +fn invalid5_alt() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["doc-dependencies"] + + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [doc-dependencies.bar] + path = "bar" + optional = true + "#, + ) + .file("src/main.rs", "") + .build(); + + p.cargo("build") + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr( + "\ +[ERROR] failed to parse manifest at `[..]` + +Caused by: + doc-dependencies are not allowed to be optional: `bar` +", + ) + .run(); +} + #[cargo_test] fn invalid6() { let p = project() @@ -1098,6 +1133,41 @@ fn optional_and_dev_dep() { .run(); } +#[cargo_test] +fn optional_and_doc_dep() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["doc-dependencies"] + + [package] + name = "test" + version = "0.1.0" + authors = [] + + [dependencies] + foo = { path = "foo", optional = true } + [doc-dependencies] + foo = { path = "foo" } + "#, + ) + .file("src/lib.rs", "") + .file("foo/Cargo.toml", &basic_manifest("foo", "0.1.0")) + .file("foo/src/lib.rs", "") + .build(); + + p.cargo("build") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[COMPILING] test v0.1.0 ([..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + #[cargo_test] fn activating_feature_activates_dep() { let p = project() @@ -1411,6 +1481,37 @@ fn only_dep_is_optional() { p.cargo("build").run(); } +#[cargo_test] +fn only_dep_is_optional_alt() { + Package::new("bar", "0.1.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["doc-dependencies"] + + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [features] + foo = ['bar'] + + [dependencies] + bar = { version = "0.1", optional = true } + + [doc-dependencies] + bar = "0.1" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build").masquerade_as_nightly_cargo().run(); +} + #[cargo_test] fn all_features_all_crates() { Package::new("bar", "0.1.0").publish(); diff --git a/tests/testsuite/features2.rs b/tests/testsuite/features2.rs index af94073da22..5a94c24136f 100644 --- a/tests/testsuite/features2.rs +++ b/tests/testsuite/features2.rs @@ -709,6 +709,66 @@ fn cyclical_dev_dep() { p.cargo("test").run(); } +#[ignore] // FIXME: overflow it's stack +#[cargo_test] +fn cyclical_doc_dep() { + // Check how a cyclical doc-dependency will work. + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["doc-dependencies"] + + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + + [features] + doc = [] + + [doc-dependencies] + foo = { path = '.', features = ["doc"] } + "#, + ) + .file( + "src/lib.rs", + r#" + pub fn assert_doc(enabled: bool) { + assert_eq!(enabled, cfg!(feature="doc")); + } + + #[test] + fn test_in_lib() { + assert_doc(true); + } + "#, + ) + .file( + "src/main.rs", + r#" + fn main() { + let expected: bool = std::env::args().skip(1).next().unwrap().parse().unwrap(); + foo::assert_doc(expected); + } + "#, + ) + .build(); + + // Old way unifies features. + p.cargo("run true").masquerade_as_nightly_cargo().run(); + // dev feature should always be enabled in tests. + p.cargo("doc").masquerade_as_nightly_cargo().run(); + + // New behavior. + switch_to_resolver_2(&p); + // Should decouple main. + p.cargo("run false").masquerade_as_nightly_cargo().run(); + + // And this should be no different. + p.cargo("doc").masquerade_as_nightly_cargo().run(); +} + #[cargo_test] fn all_feature_opts() { // All feature options at once. diff --git a/tests/testsuite/freshness.rs b/tests/testsuite/freshness.rs index 1927a690aa3..a87d84c6b1f 100644 --- a/tests/testsuite/freshness.rs +++ b/tests/testsuite/freshness.rs @@ -603,6 +603,75 @@ fn no_rebuild_transitive_target_deps() { .run(); } +#[cargo_test] +fn no_rebuild_transitive_target_deps_doc() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["doc-dependencies"] + + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + a = { path = "a" } + [doc-dependencies] + b = { path = "b" } + "#, + ) + .file("src/lib.rs", "") + .file("tests/foo.rs", "") + .file( + "a/Cargo.toml", + r#" + [package] + name = "a" + version = "0.0.1" + authors = [] + + [target.foo.dependencies] + c = { path = "../c" } + "#, + ) + .file("a/src/lib.rs", "") + .file( + "b/Cargo.toml", + r#" + [package] + name = "b" + version = "0.0.1" + authors = [] + + [dependencies] + c = { path = "../c" } + "#, + ) + .file("b/src/lib.rs", "") + .file("c/Cargo.toml", &basic_manifest("c", "0.0.1")) + .file("c/src/lib.rs", "") + .build(); + + p.cargo("build").masquerade_as_nightly_cargo().run(); + p.cargo("doc") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[CHECKING] c v0.0.1 ([..]) +[DOCUMENTING] c v0.0.1 ([..]) +[CHECKING] a v0.0.1 ([..]) +[DOCUMENTING] a v0.0.1 ([..]) +[CHECKING] b v0.0.1 ([..]) +[DOCUMENTING] b v0.0.1 ([..]) +[DOCUMENTING] foo v0.0.1 ([..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + #[cargo_test] fn rerun_if_changed_in_dep() { let p = project() diff --git a/tests/testsuite/install.rs b/tests/testsuite/install.rs index 4ba58c9a3d3..d3e12347287 100644 --- a/tests/testsuite/install.rs +++ b/tests/testsuite/install.rs @@ -1349,6 +1349,36 @@ fn dev_dependencies_lock_file_untouched() { assert!(lock == lock2, "different lockfiles"); } +#[cargo_test] +fn doc_dependencies_lock_file_untouched() { + Package::new("foo", "1.0.0").publish(); + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["doc-dependencies"] + + [package] + name = "foo" + version = "0.1.0" + authors = [] + + [doc-dependencies] + bar = { path = "a" } + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("a/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("a/src/lib.rs", "") + .build(); + + p.cargo("build").masquerade_as_nightly_cargo().run(); + let lock = p.read_lockfile(); + p.cargo("install").masquerade_as_nightly_cargo().run(); + let lock2 = p.read_lockfile(); + assert!(lock == lock2, "different lockfiles"); +} + #[cargo_test] fn install_target_native() { pkg("foo", "0.1.0"); diff --git a/tests/testsuite/metadata.rs b/tests/testsuite/metadata.rs index 63f92d8555b..9e44b193dc6 100644 --- a/tests/testsuite/metadata.rs +++ b/tests/testsuite/metadata.rs @@ -1148,7 +1148,6 @@ fn workspace_metadata_with_dependencies_and_resolve() { "bar/Cargo.toml", &r#" [package] - name = "bar" version = "0.5.0" authors = [] @@ -3523,6 +3522,8 @@ fn dep_kinds() { .file( "Cargo.toml", r#" + cargo-features = ["doc-dependencies"] + [package] name = "foo" version = "0.1.0" @@ -3530,6 +3531,9 @@ fn dep_kinds() { [dependencies] bar = "0.1" + [doc-dependencies] + bar = "0.1" + [dev-dependencies] bar = "0.1" @@ -3544,6 +3548,7 @@ fn dep_kinds() { .build(); p.cargo("metadata") + .masquerade_as_nightly_cargo() .with_json( r#" { @@ -3580,6 +3585,10 @@ fn dep_kinds() { "kind": "dev", "target": null }, + { + "kind": "doc", + "target": null + }, { "kind": "build", "target": null diff --git a/tests/testsuite/path.rs b/tests/testsuite/path.rs index 9bcd220ef77..3ef915a44f6 100644 --- a/tests/testsuite/path.rs +++ b/tests/testsuite/path.rs @@ -145,6 +145,49 @@ fn cargo_compile_with_root_dev_deps() { .run(); } +#[cargo_test] +fn cargo_compile_with_root_doc_deps() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["doc-dependencies"] + + [project] + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [doc-dependencies.bar] + version = "0.5.0" + path = "../bar" + + [[bin]] + name = "foo" + "#, + ) + .file("src/main.rs", &main_file(r#""{}", bar::gimme()"#, &["bar"])) + .build(); + let _p2 = project() + .at("bar") + .file("Cargo.toml", &basic_manifest("bar", "0.5.0")) + .file( + "src/lib.rs", + r#" + pub fn gimme() -> &'static str { + "zoidberg" + } + "#, + ) + .build(); + + p.cargo("build") + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr_contains("[..]can't find crate for `bar`") + .run(); +} + #[cargo_test] fn cargo_compile_with_root_dev_deps_with_testing() { let p = project() @@ -1136,3 +1179,78 @@ fn catch_tricky_cycle() { .with_status(101) .run(); } + +#[cargo_test] +fn catch_tricky_cycle_doc() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["doc-dependencies"] + + [package] + name = "message" + version = "0.1.0" + + [doc-dependencies] + test = { path = "test" } + "#, + ) + .file("src/lib.rs", "") + .file( + "tangle/Cargo.toml", + r#" + [package] + name = "tangle" + version = "0.1.0" + + [dependencies] + message = { path = ".." } + snapshot = { path = "../snapshot" } + "#, + ) + .file("tangle/src/lib.rs", "") + .file( + "snapshot/Cargo.toml", + r#" + [package] + name = "snapshot" + version = "0.1.0" + + [dependencies] + ledger = { path = "../ledger" } + "#, + ) + .file("snapshot/src/lib.rs", "") + .file( + "ledger/Cargo.toml", + r#" + [package] + name = "ledger" + version = "0.1.0" + + [dependencies] + tangle = { path = "../tangle" } + "#, + ) + .file("ledger/src/lib.rs", "") + .file( + "test/Cargo.toml", + r#" + [package] + name = "test" + version = "0.1.0" + + [dependencies] + snapshot = { path = "../snapshot" } + "#, + ) + .file("test/src/lib.rs", "") + .build(); + + p.cargo("doc") + .masquerade_as_nightly_cargo() + .with_stderr_contains("[..]cyclic package dependency[..]") + .with_status(101) + .run(); +} diff --git a/tests/testsuite/pub_priv.rs b/tests/testsuite/pub_priv.rs index 781716bb2e2..c0d4acca00b 100644 --- a/tests/testsuite/pub_priv.rs +++ b/tests/testsuite/pub_priv.rs @@ -205,3 +205,46 @@ Caused by: ) .run() } + +#[cargo_test] +fn pub_doc_dependency() { + Package::new("pub_dep", "0.1.0") + .file("src/lib.rs", "pub struct FromPub;") + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["public-dependency","doc-dependencies"] + + [package] + name = "foo" + version = "0.0.1" + + [doc-dependencies] + pub_dep = {version = "0.1.0", public = true} + "#, + ) + .file( + "src/lib.rs", + " + extern crate pub_dep; + pub fn use_pub(_: pub_dep::FromPub) {} + ", + ) + .build(); + + p.cargo("build --message-format=short") + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr( + "\ +error: failed to parse manifest at `[..]` + +Caused by: + 'public' specifier can only be used on regular dependencies, not Documentation dependencies +", + ) + .run() +} diff --git a/tests/testsuite/publish.rs b/tests/testsuite/publish.rs index 88f9fee8e86..fa3d58fefcb 100644 --- a/tests/testsuite/publish.rs +++ b/tests/testsuite/publish.rs @@ -1293,6 +1293,94 @@ repository = "foo" ); } +#[cargo_test] +fn publish_doc_dep_no_version() { + registry::init(); + + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["doc-dependencies"] + + [package] + name = "foo" + version = "0.1.0" + authors = [] + license = "MIT" + description = "foo" + documentation = "foo" + homepage = "foo" + repository = "foo" + + [doc-dependencies] + bar = { path = "bar" } + "#, + ) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("publish --no-verify --token sekrit") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[UPDATING] [..] +[PACKAGING] foo v0.1.0 [..] +[UPLOADING] foo v0.1.0 [..] +", + ) + .run(); + + publish::validate_upload_with_contents( + r#" + { + "authors": [], + "badges": {}, + "categories": [], + "deps": [], + "description": "foo", + "documentation": "foo", + "features": {}, + "homepage": "foo", + "keywords": [], + "license": "MIT", + "license_file": null, + "links": null, + "name": "foo", + "readme": null, + "readme_file": null, + "repository": "foo", + "vers": "0.1.0" + } + "#, + "foo-0.1.0.crate", + &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"], + &[( + "Cargo.toml", + &format!( + r#"{} +cargo-features = ["doc-dependencies"] + +[package] +name = "foo" +version = "0.1.0" +authors = [] +description = "foo" +homepage = "foo" +documentation = "foo" +license = "MIT" +repository = "foo" + +[doc-dependencies] +"#, + cargo::core::package::MANIFEST_PREAMBLE + ), + )], + ); +} + #[cargo_test] fn credentials_ambiguous_filename() { registry::init(); diff --git a/tests/testsuite/tree.rs b/tests/testsuite/tree.rs index 63115a85930..6a4f5d54ad4 100644 --- a/tests/testsuite/tree.rs +++ b/tests/testsuite/tree.rs @@ -36,6 +36,43 @@ fn make_simple_proj() -> Project { .build() } +fn make_simple_proj_with_doc() -> Project { + Package::new("c", "1.0.0").publish(); + Package::new("b", "1.0.0").dep("c", "1.0").publish(); + Package::new("a", "1.0.0").dep("b", "1.0").publish(); + Package::new("bdep", "1.0.0").dep("b", "1.0").publish(); + Package::new("devdep", "1.0.0").dep("b", "1.0.0").publish(); + Package::new("docdep", "1.0.0").dep("b", "1.0.0").publish(); + + project() + .file( + "Cargo.toml", + r#" + cargo-features = ["doc-dependencies"] + + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + a = "1.0" + c = "1.0" + + [build-dependencies] + bdep = "1.0" + + [dev-dependencies] + devdep = "1.0" + + [doc-dependencies] + docdep = "1.0" + "#, + ) + .file("src/lib.rs", "") + .file("build.rs", "fn main() {}") + .build() +} + #[cargo_test] fn simple() { // A simple test with a few different dependencies. @@ -70,6 +107,45 @@ bdep v1.0.0 .run(); } +#[cargo_test] +fn simple_with_doc() { + // A simple test with a few different dependencies. + let p = make_simple_proj_with_doc(); + + p.cargo("tree") + .masquerade_as_nightly_cargo() + .with_stdout( + "\ +foo v0.1.0 ([..]/foo) +├── a v1.0.0 +│ └── b v1.0.0 +│ └── c v1.0.0 +└── c v1.0.0 +[build-dependencies] +└── bdep v1.0.0 + └── b v1.0.0 (*) +[dev-dependencies] +└── devdep v1.0.0 + └── b v1.0.0 (*) +[doc-dependencies] +└── docdep v1.0.0 + └── b v1.0.0 (*) +", + ) + .run(); + + p.cargo("tree -p bdep") + .masquerade_as_nightly_cargo() + .with_stdout( + "\ +bdep v1.0.0 +└── b v1.0.0 + └── c v1.0.0 +", + ) + .run(); +} + #[cargo_test] fn virtual_workspace() { // Multiple packages in a virtual workspace. From 3282447f294738f6b2958cd5ddf01cd182728102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20BRANSTETT?= Date: Tue, 1 Mar 2022 01:00:39 +0100 Subject: [PATCH 4/7] Fix resolver test crate build issue --- crates/resolver-tests/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/resolver-tests/src/lib.rs b/crates/resolver-tests/src/lib.rs index f47017d1d2a..cca3fa61874 100644 --- a/crates/resolver-tests/src/lib.rs +++ b/crates/resolver-tests/src/lib.rs @@ -696,6 +696,7 @@ impl fmt::Debug for PrettyPrintRegistry { d.version_req(), match d.kind() { DepKind::Development => "DepKind::Development", + DepKind::Documentation => "DepKind::Documentation", DepKind::Build => "DepKind::Build", DepKind::Normal => "DepKind::Normal", }, From 0d961123d132766bc0f9321a253900bdd20603be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20BRANSTETT?= Date: Tue, 1 Mar 2022 01:10:35 +0100 Subject: [PATCH 5/7] Fix other test issues --- src/cargo/ops/fix.rs | 2 +- tests/testsuite/freshness.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cargo/ops/fix.rs b/src/cargo/ops/fix.rs index e8459d634ed..0935a497297 100644 --- a/src/cargo/ops/fix.rs +++ b/src/cargo/ops/fix.rs @@ -302,7 +302,7 @@ fn check_resolver_change(ws: &Workspace<'_>, opts: &FixOptions) -> CargoResult<( // FIXME: Include doc-dependencies when stable drop_eprintln!( config, - "The following differences only apply when building with dev-dependencies or doc-dependencies:\n" + "The following differences only apply when building with dev-dependencies:\n" ); show_diffs(with_transitive_diffs); } diff --git a/tests/testsuite/freshness.rs b/tests/testsuite/freshness.rs index a87d84c6b1f..c1973ee4d35 100644 --- a/tests/testsuite/freshness.rs +++ b/tests/testsuite/freshness.rs @@ -661,8 +661,8 @@ fn no_rebuild_transitive_target_deps_doc() { "\ [CHECKING] c v0.0.1 ([..]) [DOCUMENTING] c v0.0.1 ([..]) -[CHECKING] a v0.0.1 ([..]) -[DOCUMENTING] a v0.0.1 ([..]) +[..] a v0.0.1 ([..]) +[..] a v0.0.1 ([..]) [CHECKING] b v0.0.1 ([..]) [DOCUMENTING] b v0.0.1 ([..]) [DOCUMENTING] foo v0.0.1 ([..]) From fd925061e21b4808b0b46a6b394fa9f2ae6e3c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20BRANSTETT?= Date: Tue, 1 Mar 2022 03:00:35 +0100 Subject: [PATCH 6/7] Fix a build issue in benches --- benches/benchsuite/benches/resolve.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/benches/benchsuite/benches/resolve.rs b/benches/benchsuite/benches/resolve.rs index 266c9c93a9c..99748e8ef72 100644 --- a/benches/benchsuite/benches/resolve.rs +++ b/benches/benchsuite/benches/resolve.rs @@ -1,6 +1,6 @@ use cargo::core::compiler::{CompileKind, RustcTargetData}; use cargo::core::resolver::features::{CliFeatures, FeatureOpts, FeatureResolver, ForceAllTargets}; -use cargo::core::resolver::{HasDevUnits, ResolveBehavior}; +use cargo::core::resolver::{HasTransitiveUnits, ResolveBehavior}; use cargo::core::{PackageIdSpec, Workspace}; use cargo::ops::WorkspaceResolve; use cargo::Config; @@ -147,7 +147,7 @@ struct ResolveInfo<'cfg> { target_data: RustcTargetData<'cfg>, cli_features: CliFeatures, specs: Vec, - has_dev_units: HasDevUnits, + has_dev_units: HasTransitiveUnits, force_all_targets: ForceAllTargets, ws_resolve: WorkspaceResolve<'cfg>, } @@ -186,7 +186,7 @@ fn do_resolve<'cfg>(config: &'cfg Config, ws_root: &Path) -> ResolveInfo<'cfg> { let cli_features = CliFeatures::from_command_line(&[], false, true).unwrap(); let pkgs = cargo::ops::Packages::Default; let specs = pkgs.to_package_id_specs(&ws).unwrap(); - let has_dev_units = HasDevUnits::Yes; + let has_dev_units = HasTransitiveUnits::Yes; let force_all_targets = ForceAllTargets::No; // Do an initial run to download anything necessary so that it does // not confuse criterion's warmup. From 533846dae1a3ace3f947a1bd4667df093113c132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20BRANSTETT?= Date: Tue, 1 Mar 2022 15:40:39 +0100 Subject: [PATCH 7/7] Fix cyclical doc-dependencies not detected causing stack-overflow --- src/cargo/core/resolver/mod.rs | 10 ++++---- tests/testsuite/build_script.rs | 7 ++++-- tests/testsuite/features2.rs | 41 ++++++++++----------------------- 3 files changed, 23 insertions(+), 35 deletions(-) diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index b677d8f8dbf..04b7a74edf6 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -54,6 +54,7 @@ use std::time::{Duration, Instant}; use log::{debug, trace}; +use crate::core::dependency::DepKind; use crate::core::PackageIdSpec; use crate::core::{Dependency, PackageId, Registry, Summary}; use crate::util::config::Config; @@ -1009,13 +1010,14 @@ fn check_cycles(resolve: &Resolve) -> CargoResult<()> { for id in resolve.iter() { let map = graph.entry(id).or_insert_with(BTreeMap::new); for (dep_id, listings) in resolve.deps_not_replaced(id) { - let transitive_dep = listings.iter().find(|d| d.is_transitive()); + // We explitly don't use is_transitive() as doc-deps can create cycles. + let not_dev_dep = listings.iter().find(|d| d.kind() != DepKind::Development); - if let Some(transitive_dep) = transitive_dep.cloned() { - map.insert(dep_id, transitive_dep.clone()); + if let Some(not_dev_dep) = not_dev_dep.cloned() { + map.insert(dep_id, not_dev_dep.clone()); resolve .replacement(dep_id) - .map(|p| map.insert(p, transitive_dep)); + .map(|p| map.insert(p, not_dev_dep)); } } } diff --git a/tests/testsuite/build_script.rs b/tests/testsuite/build_script.rs index b7a3a35e5c9..326933cd83a 100644 --- a/tests/testsuite/build_script.rs +++ b/tests/testsuite/build_script.rs @@ -4690,7 +4690,6 @@ fn dev_dep_with_links() { p.cargo("check --tests").run() } -#[ignore] // FIXME: overflow it's stack #[cargo_test] fn doc_dep_with_links() { let p = project() @@ -4728,7 +4727,11 @@ fn doc_dep_with_links() { .file("bar/src/lib.rs", "") .build(); - p.cargo("doc").masquerade_as_nightly_cargo().run() + p.cargo("doc") + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr_contains("[..]cyclic package dependency[..]") + .run() } #[cargo_test] diff --git a/tests/testsuite/features2.rs b/tests/testsuite/features2.rs index 5a94c24136f..8149a27c321 100644 --- a/tests/testsuite/features2.rs +++ b/tests/testsuite/features2.rs @@ -709,7 +709,6 @@ fn cyclical_dev_dep() { p.cargo("test").run(); } -#[ignore] // FIXME: overflow it's stack #[cargo_test] fn cyclical_doc_dep() { // Check how a cyclical doc-dependency will work. @@ -731,42 +730,26 @@ fn cyclical_doc_dep() { foo = { path = '.', features = ["doc"] } "#, ) - .file( - "src/lib.rs", - r#" - pub fn assert_doc(enabled: bool) { - assert_eq!(enabled, cfg!(feature="doc")); - } - - #[test] - fn test_in_lib() { - assert_doc(true); - } - "#, - ) - .file( - "src/main.rs", - r#" - fn main() { - let expected: bool = std::env::args().skip(1).next().unwrap().parse().unwrap(); - foo::assert_doc(expected); - } - "#, - ) + .file("src/lib.rs", "") + .file("src/main.rs", "") .build(); // Old way unifies features. - p.cargo("run true").masquerade_as_nightly_cargo().run(); - // dev feature should always be enabled in tests. - p.cargo("doc").masquerade_as_nightly_cargo().run(); + p.cargo("doc") + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr_contains("[..]cyclic package dependency[..]") + .run(); // New behavior. switch_to_resolver_2(&p); - // Should decouple main. - p.cargo("run false").masquerade_as_nightly_cargo().run(); // And this should be no different. - p.cargo("doc").masquerade_as_nightly_cargo().run(); + p.cargo("doc") + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr_contains("[..]cyclic package dependency[..]") + .run(); } #[cargo_test]