diff --git a/src/cargo/core/compiler/build_context/mod.rs b/src/cargo/core/compiler/build_context/mod.rs index 858f7ae9074..9b55fedb88c 100644 --- a/src/cargo/core/compiler/build_context/mod.rs +++ b/src/cargo/core/compiler/build_context/mod.rs @@ -93,6 +93,11 @@ impl<'a, 'cfg> BuildContext<'a, 'cfg> { .extern_crate_name(unit.pkg.package_id(), dep.pkg.package_id(), dep.target) } + pub fn is_public_dependency(&self, unit: &Unit<'a>, dep: &Unit<'a>) -> bool { + self.resolve + .is_public_dep(unit.pkg.package_id(), dep.pkg.package_id()) + } + /// Whether a dependency should be compiled for the host or target platform, /// specified by `Kind`. pub fn dep_platform_activated(&self, dep: &Dependency, kind: Kind) -> bool { diff --git a/src/cargo/core/compiler/fingerprint.rs b/src/cargo/core/compiler/fingerprint.rs index e7a2b378542..c504ee68e67 100644 --- a/src/cargo/core/compiler/fingerprint.rs +++ b/src/cargo/core/compiler/fingerprint.rs @@ -315,11 +315,13 @@ pub fn prepare_target<'a, 'cfg>( /// A compilation unit dependency has a fingerprint that is comprised of: /// * its package ID /// * its extern crate name +/// * its public/private status /// * its calculated fingerprint for the dependency #[derive(Clone)] struct DepFingerprint { pkg_id: u64, name: String, + public: bool, fingerprint: Arc, } @@ -420,7 +422,7 @@ impl Serialize for DepFingerprint { where S: ser::Serializer, { - (&self.pkg_id, &self.name, &self.fingerprint.hash()).serialize(ser) + (&self.pkg_id, &self.name, &self.public, &self.fingerprint.hash()).serialize(ser) } } @@ -429,10 +431,11 @@ impl<'de> Deserialize<'de> for DepFingerprint { where D: de::Deserializer<'de>, { - let (pkg_id, name, hash) = <(u64, String, u64)>::deserialize(d)?; + let (pkg_id, name, public, hash) = <(u64, String, bool, u64)>::deserialize(d)?; Ok(DepFingerprint { pkg_id, name, + public, fingerprint: Arc::new(Fingerprint { memoized_hash: Mutex::new(Some(hash)), ..Fingerprint::new() @@ -850,11 +853,13 @@ impl hash::Hash for Fingerprint { for DepFingerprint { pkg_id, name, + public, fingerprint, } in deps { pkg_id.hash(h); name.hash(h); + public.hash(h); // use memoized dep hashes to avoid exponential blowup h.write_u64(Fingerprint::hash(fingerprint)); } @@ -900,6 +905,7 @@ impl DepFingerprint { ) -> CargoResult { let fingerprint = calculate(cx, dep)?; let name = cx.bcx.extern_crate_name(parent, dep)?; + let public = cx.bcx.is_public_dependency(parent, dep); // We need to be careful about what we hash here. We have a goal of // supporting renaming a project directory and not rebuilding @@ -920,6 +926,7 @@ impl DepFingerprint { Ok(DepFingerprint { pkg_id, name, + public, fingerprint, }) } diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 9c9f6533b92..e2107b8a97c 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -23,6 +23,7 @@ use log::debug; use same_file::is_same_file; use serde::Serialize; +use crate::core::Feature; pub use self::build_config::{BuildConfig, CompileMode, MessageFormat}; pub use self::build_context::{BuildContext, FileFlavor, TargetConfig, TargetInfo}; use self::build_plan::BuildPlan; @@ -966,15 +967,24 @@ fn build_deps_args<'a, 'cfg>( } } + let mut unstable_opts = false; + for dep in dep_targets { if dep.mode.is_run_custom_build() { cmd.env("OUT_DIR", &cx.files().build_script_out_dir(&dep)); } if dep.target.linkable() && !dep.mode.is_doc() { - link_to(cmd, cx, unit, &dep)?; + link_to(cmd, cx, unit, &dep, &mut unstable_opts)?; } } + // This will only be set if we're already usign a feature + // requiring nightly rust + if unstable_opts { + cmd.arg("-Z").arg("unstable-options"); + } + + return Ok(()); fn link_to<'a, 'cfg>( @@ -982,6 +992,7 @@ fn build_deps_args<'a, 'cfg>( cx: &mut Context<'a, 'cfg>, current: &Unit<'a>, dep: &Unit<'a>, + need_unstable_opts: &mut bool ) -> CargoResult<()> { let bcx = cx.bcx; for output in cx.outputs(dep)?.iter() { @@ -995,7 +1006,17 @@ fn build_deps_args<'a, 'cfg>( v.push(cx.files().out_dir(dep)); v.push(&path::MAIN_SEPARATOR.to_string()); v.push(&output.path.file_name().unwrap()); - cmd.arg("--extern").arg(&v); + + if current.pkg.manifest().features().require(Feature::public_dependency()).is_ok() && + !bcx.is_public_dependency(current, dep) { + + cmd.arg("--extern-private"); + *need_unstable_opts = true; + } else { + cmd.arg("--extern"); + } + + cmd.arg(&v); } Ok(()) } diff --git a/src/cargo/core/dependency.rs b/src/cargo/core/dependency.rs index b779d77c693..f1a87e0a6f9 100644 --- a/src/cargo/core/dependency.rs +++ b/src/cargo/core/dependency.rs @@ -301,6 +301,10 @@ impl Dependency { /// Sets whether the dependency is public. pub fn set_public(&mut self, public: bool) -> &mut Dependency { + if public { + // Setting 'public' only makes sense for normal dependencies + assert_eq!(self.kind(), Kind::Normal); + } Rc::make_mut(&mut self.inner).public = public; self } diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 59787c3c3e8..3d0ae9b1759 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -199,6 +199,9 @@ features! { // Declarative build scripts. [unstable] metabuild: bool, + + // Specifying the 'public' attribute on dependencies + [unstable] public_dependency: bool, } } diff --git a/src/cargo/core/resolver/resolve.rs b/src/cargo/core/resolver/resolve.rs index 04261d1f398..a2acb32c755 100644 --- a/src/cargo/core/resolver/resolve.rs +++ b/src/cargo/core/resolver/resolve.rs @@ -6,6 +6,7 @@ use std::iter::FromIterator; use url::Url; use crate::core::{Dependency, PackageId, PackageIdSpec, Summary, Target}; +use crate::core::dependency::Kind; use crate::util::errors::CargoResult; use crate::util::Graph; @@ -29,6 +30,8 @@ pub struct Resolve { checksums: HashMap>, metadata: Metadata, unused_patches: Vec, + // A map from packages to a set of their public dependencies + public_dependencies: HashMap>, } impl Resolve { @@ -41,6 +44,21 @@ impl Resolve { unused_patches: Vec, ) -> Resolve { let reverse_replacements = replacements.iter().map(|(&p, &r)| (r, p)).collect(); + let public_dependencies = graph.iter().map(|p| { + let public_deps = graph.edges(p).flat_map(|(dep_package, deps)| { + let id_opt: Option = deps.iter().find(|d| d.kind() == Kind::Normal).and_then(|d| { + if d.is_public() { + Some(dep_package.clone()) + } else { + None + } + }); + id_opt + }).collect::>(); + + (p.clone(), public_deps) + }).collect(); + Resolve { graph, replacements, @@ -50,6 +68,7 @@ impl Resolve { unused_patches, empty_features: HashSet::new(), reverse_replacements, + public_dependencies } } @@ -197,6 +216,12 @@ unable to verify that `{0}` is the same as when the lockfile was generated self.features.get(&pkg).unwrap_or(&self.empty_features) } + pub fn is_public_dep(&self, pkg: PackageId, dep: PackageId) -> bool { + self.public_dependencies.get(&pkg) + .map(|public_deps| public_deps.contains(&dep)) + .unwrap_or_else(|| panic!("Unknown dependency {:?} for package {:?}", dep, pkg)) + } + pub fn features_sorted(&self, pkg: PackageId) -> Vec<&str> { let mut v = Vec::from_iter(self.features(pkg).iter().map(|s| s.as_ref())); v.sort_unstable(); diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index 7d71afde402..304e6f86fa7 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -8,6 +8,7 @@ use glob::glob; use log::debug; use url::Url; +use crate::core::features::Features; use crate::core::profiles::Profiles; use crate::core::registry::PackageRegistry; use crate::core::{Dependency, PackageIdSpec, PackageId}; @@ -540,6 +541,13 @@ impl<'cfg> Workspace<'cfg> { Ok(()) } + pub fn features(&self) -> &Features { + match self.root_maybe() { + MaybePackage::Package(p) => p.manifest().features(), + MaybePackage::Virtual(vm) => vm.features(), + } + } + /// Validates a workspace, ensuring that a number of invariants are upheld: /// /// 1. A workspace only has one root. @@ -547,10 +555,7 @@ impl<'cfg> Workspace<'cfg> { /// 3. The current crate is a member of this workspace. fn validate(&mut self) -> CargoResult<()> { // Validate config profiles only once per workspace. - let features = match self.root_maybe() { - MaybePackage::Package(p) => p.manifest().features(), - MaybePackage::Virtual(vm) => vm.features(), - }; + let features = self.features(); let mut warnings = Vec::new(); self.config.profiles()?.validate(features, &mut warnings)?; for warning in warnings { diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index edb7aca9cbd..f38f21c8dcb 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -18,6 +18,7 @@ use crate::core::resolver::Method; use crate::core::{ Package, PackageId, PackageIdSpec, PackageSet, Resolve, Source, SourceId, Verbosity, Workspace, }; +use crate::core::Feature; use crate::ops; use crate::sources::PathSource; use crate::util::errors::{CargoResult, CargoResultExt}; @@ -590,6 +591,14 @@ fn run_verify(ws: &Workspace<'_>, tar: &FileLock, opts: &PackageOpts<'_>) -> Car let pkg_fingerprint = hash_all(&dst)?; let ws = Workspace::ephemeral(new_pkg, config, None, true)?; + let rustc_args = if pkg.manifest().features().require(Feature::public_dependency()).is_ok() { + // FIXME: Turn this on at some point in the future + //Some(vec!["-D exported_private_dependencies".to_string()]) + None + } else { + None + }; + let exec: Arc = Arc::new(DefaultExecutor); ops::compile_ws( &ws, @@ -604,7 +613,7 @@ fn run_verify(ws: &Workspace<'_>, tar: &FileLock, opts: &PackageOpts<'_>) -> Car required_features_filterable: true, }, target_rustdoc_args: None, - target_rustc_args: None, + target_rustc_args: rustc_args, local_rustdoc_args: None, export_dir: None, }, diff --git a/src/cargo/ops/resolve.rs b/src/cargo/ops/resolve.rs index 48acfeaab35..ac6759c8a06 100644 --- a/src/cargo/ops/resolve.rs +++ b/src/cargo/ops/resolve.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; use log::{debug, trace}; +use crate::core::Feature; use crate::core::registry::PackageRegistry; use crate::core::resolver::{self, Method, Resolve}; use crate::core::{PackageId, PackageIdSpec, PackageSet, Source, SourceId, Workspace}; @@ -330,7 +331,7 @@ pub fn resolve_with_previous<'cfg>( registry, &try_to_use, Some(ws.config()), - false, // TODO: use "public and private dependencies" feature flag + ws.features().require(Feature::public_dependency()).is_ok(), )?; resolved.register_used_patches(registry.patches()); if register_patches { diff --git a/src/cargo/sources/registry/mod.rs b/src/cargo/sources/registry/mod.rs index a2c6d4f9ce7..e4c2d027474 100644 --- a/src/cargo/sources/registry/mod.rs +++ b/src/cargo/sources/registry/mod.rs @@ -285,6 +285,7 @@ struct RegistryDependency<'a> { kind: Option>, registry: Option>, package: Option>, + public: Option } impl<'a> RegistryDependency<'a> { @@ -300,6 +301,7 @@ impl<'a> RegistryDependency<'a> { kind, registry, package, + public } = self; let id = if let Some(registry) = ®istry { @@ -324,6 +326,9 @@ impl<'a> RegistryDependency<'a> { None => None, }; + // All dependencies are private by default + let public = public.unwrap_or(false); + // Unfortunately older versions of cargo and/or the registry ended up // publishing lots of entries where the features array contained the // empty feature, "", inside. This confuses the resolution process much @@ -341,7 +346,8 @@ impl<'a> RegistryDependency<'a> { .set_default_features(default_features) .set_features(features) .set_platform(platform) - .set_kind(kind); + .set_kind(kind) + .set_public(public); Ok(dep) } diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index be2baca7ae3..10311359f62 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -239,6 +239,7 @@ pub struct DetailedTomlDependency { #[serde(rename = "default_features")] default_features2: Option, package: Option, + public: Option } #[derive(Debug, Deserialize, Serialize)] @@ -1461,6 +1462,16 @@ impl DetailedTomlDependency { cx.features.require(Feature::rename_dependency())?; dep.set_explicit_name_in_toml(name_in_toml); } + + if let Some(p) = self.public { + cx.features.require(Feature::public_dependency())?; + + if dep.kind() != Kind::Normal { + bail!("'public' specifier can only be used on regular dependencies, not {:?} dependencies", dep.kind()); + } + + dep.set_public(p); + } Ok(dep) } } diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 9a740d75881..b78cb5c7d95 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -264,3 +264,20 @@ conflicting binaries from another package. Additionally, a new flag `--no-track` is available to prevent `cargo install` from writing tracking information in `$CARGO_HOME` about which packages are installed. + +### public-dependency +* Tracking Issue: [#44663](https://github.com/rust-lang/rust/issues/44663) + +The 'public-dependency' features allows marking dependencies as 'public' +or 'private'. When this feature is enabled, additional information is passed to rustc to allow +the 'exported_private_dependencies' lint to function properly. + +This requires the appropriate key to be set in `cargo-features`: + +```toml +cargo-features = ["public-dependency"] + +[dependencies] +my_dep = { version = "1.2.3", public = true } +private_dep = "2.0.0" # Will be 'private' by default +``` diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index ddc922ce894..74c767a150c 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -67,6 +67,7 @@ mod profile_config; mod profile_overrides; mod profile_targets; mod profiles; +mod pub_priv; mod publish; mod publish_lockfile; mod read_manifest; diff --git a/tests/testsuite/pub_priv.rs b/tests/testsuite/pub_priv.rs new file mode 100644 index 00000000000..894f7775661 --- /dev/null +++ b/tests/testsuite/pub_priv.rs @@ -0,0 +1,205 @@ +use crate::support::registry::Package; +use crate::support::{is_nightly, project}; + +#[test] +fn exported_priv_warning() { + if !is_nightly() { + return; + } + Package::new("priv_dep", "0.1.0") + .file("src/lib.rs", "pub struct FromPriv;") + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["public-dependency"] + + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + priv_dep = "0.1.0" + "#, + ) + .file( + "src/lib.rs", + " + extern crate priv_dep; + pub fn use_priv(_: priv_dep::FromPriv) {} + ", + ) + .build(); + + p.cargo("build --message-format=short") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[UPDATING] `[..]` index +[DOWNLOADING] crates ... +[DOWNLOADED] priv_dep v0.1.0 ([..]) +[COMPILING] priv_dep v0.1.0 +[COMPILING] foo v0.0.1 ([CWD]) +src/lib.rs:3:13: warning: type `priv_dep::FromPriv` from private dependency 'priv_dep' in public interface +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +" + ) + .run() +} + +#[test] +fn exported_pub_dep() { + if !is_nightly() { + return; + } + 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"] + + [package] + name = "foo" + version = "0.0.1" + + [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_stderr( + "\ +[UPDATING] `[..]` index +[DOWNLOADING] crates ... +[DOWNLOADED] pub_dep v0.1.0 ([..]) +[COMPILING] pub_dep v0.1.0 +[COMPILING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run() +} + +#[test] +pub fn requires_nightly_cargo() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["public-dependency"] + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build --message-format=short") + .with_status(101) + .with_stderr( + "\ +error: failed to parse manifest at `[..]` + +Caused by: + the cargo feature `public-dependency` requires a nightly version of Cargo, but this is the `stable` channel +See https://doc.rust-lang.org/book/appendix-07-nightly-rust.html for more information about Rust release channels. +" + ) + .run() +} + +#[test] +fn requires_feature() { + Package::new("pub_dep", "0.1.0") + .file("src/lib.rs", "") + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + pub_dep = { version = "0.1.0", public = true } + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build --message-format=short") + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr( + "\ +error: failed to parse manifest at `[..]` + +Caused by: + feature `public-dependency` is required + +consider adding `cargo-features = [\"public-dependency\"]` to the manifest +", + ) + .run() +} + + +#[test] +fn pub_dev_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"] + + [package] + name = "foo" + version = "0.0.1" + + [dev-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 Development dependencies +", + ) + .run() +} diff --git a/tests/testsuite/support/resolver.rs b/tests/testsuite/support/resolver.rs index 84bbf3d8317..fd5dc22db02 100644 --- a/tests/testsuite/support/resolver.rs +++ b/tests/testsuite/support/resolver.rs @@ -348,8 +348,8 @@ fn meta_test_deep_pretty_print_registry() { pkg!(("baz", "1.0.1")), pkg!(("cat", "1.0.2") => [dep_req_kind("other", "2", Kind::Build, false)]), pkg!(("cat", "1.0.3") => [dep_req_kind("other", "2", Kind::Development, false)]), - pkg!(("cat", "1.0.4") => [dep_req_kind("other", "2", Kind::Build, true)]), - pkg!(("cat", "1.0.5") => [dep_req_kind("other", "2", Kind::Development, true)]), + pkg!(("cat", "1.0.4") => [dep_req_kind("other", "2", Kind::Build, false)]), + pkg!(("cat", "1.0.5") => [dep_req_kind("other", "2", Kind::Development, false)]), pkg!(("dep_req", "1.0.0")), pkg!(("dep_req", "2.0.0")), ]) @@ -363,8 +363,8 @@ fn meta_test_deep_pretty_print_registry() { pkg!((\"baz\", \"1.0.1\")),\ pkg!((\"cat\", \"1.0.2\") => [dep_req_kind(\"other\", \"^2\", Kind::Build, false),]),\ pkg!((\"cat\", \"1.0.3\") => [dep_req_kind(\"other\", \"^2\", Kind::Development, false),]),\ - pkg!((\"cat\", \"1.0.4\") => [dep_req_kind(\"other\", \"^2\", Kind::Build, true),]),\ - pkg!((\"cat\", \"1.0.5\") => [dep_req_kind(\"other\", \"^2\", Kind::Development, true),]),\ + pkg!((\"cat\", \"1.0.4\") => [dep_req_kind(\"other\", \"^2\", Kind::Build, false),]),\ + pkg!((\"cat\", \"1.0.5\") => [dep_req_kind(\"other\", \"^2\", Kind::Development, false),]),\ pkg!((\"dep_req\", \"1.0.0\")),\ pkg!((\"dep_req\", \"2.0.0\")),]" )